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>
        <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
    protected void 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]+")) {
                            throw new OkapiException("Invalid characters in data");
                        }
                        break;
                    case 18: formatControlCode = "62";
                        break;
                    case 23: formatControlCode = "62";
                        if (!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 (content.length() > 8) {
                    throw new OkapiException("Auspost input is too long");
                } else {
                    formatControlCode = "45";
                }
                break;
            case AUSROUTE:
                if (content.length() > 8) {
                    throw new OkapiException("Auspost input is too long");
                } else {
                    formatControlCode = "87";
                }
                break;
            case AUSREDIRECT:
                if (content.length() > 8) {
                    throw new OkapiException("Auspost input is too long");
                } else {
                    formatControlCode = "92";
                }
                break;
        }

        infoLine("FCC: " + formatControlCode);

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

        if (!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();

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

    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() - 1) * 2) + 1; // no whitespace needed after the final bar
        symbol_height = 8;
    }
}
 public class AztecCode extends Symbol {

    /* 27 x 27 data grid */
    private static final int[] COMPACT_AZTEC_MAP = {
        609, 608, 411, 413, 415, 417, 419, 421, 423, 425, 427, 429, 431, 433, 435, 437, 439, 441, 443, 445, 447, 449, 451, 453, 455, 457, 459,
        607, 606, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458,
        605, 604, 409, 408, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 460, 461,
        603, 602, 407, 406, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 462, 463,
        601, 600, 405, 404, 241, 240, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 284, 285, 464, 465,
        599, 598, 403, 402, 239, 238, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 286, 287, 466, 467,
        597, 596, 401, 400, 237, 236, 105, 104, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 140, 141, 288, 289, 468, 469,
        595, 594, 399, 398, 235, 234, 103, 102, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 142, 143, 290, 291, 470, 471,
        593, 592, 397, 396, 233, 232, 101, 100, 1, 1, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 0, 1, 28, 29, 144, 145, 292, 293, 472, 473,
        591, 590, 395, 394, 231, 230, 99, 98, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 31, 146, 147, 294, 295, 474, 475,
        589, 588, 393, 392, 229, 228, 97, 96, 2027, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2007, 32, 33, 148, 149, 296, 297, 476, 477,
        587, 586, 391, 390, 227, 226, 95, 94, 2026, 1, 0, 1, 1, 1, 1, 1, 0, 1, 2008, 34, 35, 150, 151, 298, 299, 478, 479,
        585, 584, 389, 388, 225, 224, 93, 92, 2025, 1, 0, 1, 0, 0, 0, 1, 0, 1, 2009, 36, 37, 152, 153, 300, 301, 480, 481,
        583, 582, 387, 386, 223, 222, 91, 90, 2024, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2010, 38, 39, 154, 155, 302, 303, 482, 483,
        581, 580, 385, 384, 221, 220, 89, 88, 2023, 1, 0, 1, 0, 0, 0, 1, 0, 1, 2011, 40, 41, 156, 157, 304, 305, 484, 485,
        579, 578, 383, 382, 219, 218, 87, 86, 2022, 1, 0, 1, 1, 1, 1, 1, 0, 1, 2012, 42, 43, 158, 159, 306, 307, 486, 487,
        577, 576, 381, 380, 217, 216, 85, 84, 2021, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2013, 44, 45, 160, 161, 308, 309, 488, 489,
        575, 574, 379, 378, 215, 214, 83, 82, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 46, 47, 162, 163, 310, 311, 490, 491,
        573, 572, 377, 376, 213, 212, 81, 80, 0, 0, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 0, 0, 48, 49, 164, 165, 312, 313, 492, 493,
        571, 570, 375, 374, 211, 210, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58, 56, 54, 50, 51, 166, 167, 314, 315, 494, 495,
        569, 568, 373, 372, 209, 208, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 55, 52, 53, 168, 169, 316, 317, 496, 497,
        567, 566, 371, 370, 206, 204, 202, 200, 198, 196, 194, 192, 190, 188, 186, 184, 182, 180, 178, 176, 174, 170, 171, 318, 319, 498, 499,
        565, 564, 369, 368, 207, 205, 203, 201, 199, 197, 195, 193, 191, 189, 187, 185, 183, 181, 179, 177, 175, 172, 173, 320, 321, 500, 501,
        563, 562, 366, 364, 362, 360, 358, 356, 354, 352, 350, 348, 346, 344, 342, 340, 338, 336, 334, 332, 330, 328, 326, 322, 323, 502, 503,
        561, 560, 367, 365, 363, 361, 359, 357, 355, 353, 351, 349, 347, 345, 343, 341, 339, 337, 335, 333, 331, 329, 327, 324, 325, 504, 505,
        558, 556, 554, 552, 550, 548, 546, 544, 542, 540, 538, 536, 534, 532, 530, 528, 526, 524, 522, 520, 518, 516, 514, 512, 510, 506, 507,
        559, 557, 555, 553, 551, 549, 547, 545, 543, 541, 539, 537, 535, 533, 531, 529, 527, 525, 523, 521, 519, 517, 515, 513, 511, 508, 509
    };

    private static final int[][] AZTEC_MAP = new int[151][151];

    /* From Table 2:
     *
     * 1 = upper
     * 2 = lower
     * 4 = mixed
     * 8 = punctuation
     * 16 = digits
     * 32 = binary
     *
     * Values can be OR'ed, so e.g. 12 = 4 | 8, and 23 = 1 | 2 | 4 | 16
     */
    private static final int[] AZTEC_CODE_SET = {
        32, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 4, 4, 4, 4, 4, 23, 8, 8, 8, 8, 8, 8, 8,
        8, 8, 8, 8, 24, 8, 24, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8,
        8, 8, 8, 8, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 4, 8, 4, 4, 4, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 8, 4, 8, 4, 4
    };

    /* From Table 2 */
    private static final int[] AZTEC_SYMBOL_CHAR = {
        0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 300, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 15, 16, 17, 18, 19, 1, 6, 7, 8, 9, 10, 11, 12,
        13, 14, 15, 16, 301, 18, 302, 20, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 21, 22,
        23, 24, 25, 26, 20, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 27, 21, 28, 22, 23, 24, 2, 3, 4,
        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
        25, 26, 27, 29, 25, 30, 26, 27
    };

    /* Problem characters are:
     * 300: Carriage Return (ASCII 13)
     * 301: Comma (ASCII 44)
     * 302: Full Stop (ASCII 46)
     */
    private static final String[] PENTBIT = {
        "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", "01000", "01001",
        "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001", "10010", "10011", "10100", "10101",
        "10110", "10111", "11000", "11001", "11010", "11011", "11100", "11101", "11110", "11111"
    };

    private static final String[] QUADBIT = {
        "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001",
        "1010", "1011", "1100", "1101", "1110", "1111"
    };

    private static final String[] TRIBIT = {
        "000", "001", "010", "011", "100", "101", "110", "111"
    };

    /* Codewords per symbol */
    private static final int[] AZTEC_SIZES = {
        21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790,
        864, 940, 1020, 920, 992, 1066, 1144, 1224, 1306, 1392, 1480, 1570, 1664
    };

    private static final int[] AZTEC_COMPACT_SIZES = {
        17, 40, 51, 76
    };

    /* Data bits per symbol maximum with 10% error correction */
    private static final int[] AZTEC_10_DATA_SIZES = {
        96, 246, 408, 616, 840, 1104, 1392, 1704, 2040, 2420, 2820, 3250, 3720, 4200, 4730,
        5270, 5840, 6450, 7080, 7750, 8430, 9150, 9900, 10680, 11484, 12324, 13188, 14076,
        15000, 15948, 16920, 17940
    };

    /* Data bits per symbol maximum with 23% error correction */
    private static final int[] AZTEC_23_DATA_SIZES = {
        84, 204, 352, 520, 720, 944, 1184, 1456, 1750, 2070, 2410, 2780, 3180, 3590, 4040,
        4500, 5000, 5520, 6060, 6630, 7210, 7830, 8472, 9132, 9816, 10536, 11280, 12036,
        12828, 13644, 14472, 15348
    };

    /* Data bits per symbol maximum with 36% error correction */
    private static final int[] AZTEC_36_DATA_SIZES = {
        66, 168, 288, 432, 592, 776, 984, 1208, 1450, 1720, 2000, 2300, 2640, 2980, 3350,
        3740, 4150, 4580, 5030, 5500, 5990, 6500, 7032, 7584, 8160, 8760, 9372, 9996, 10656,
        11340, 12024, 12744
    };

    /* Data bits per symbol maximum with 50% error correction */
    private static final int[] AZTEC_50_DATA_SIZES = {
        48, 126, 216, 328, 456, 600, 760, 936, 1120, 1330, 1550, 1790, 2050, 2320, 2610,
        2910, 3230, 3570, 3920, 4290, 4670, 5070, 5484, 5916, 6360, 6828, 7308, 7800, 8316,
        8844, 9384, 9948
    };

    private static final int[] AZTEC_COMPACT_10_DATA_SIZES = {
        78, 198, 336, 520
    };
    private static final int[] AZTEC_COMPACT_23_DATA_SIZES = {
        66, 168, 288, 440
    };
    private static final int[] AZTEC_COMPACT_36_DATA_SIZES = {
        48, 138, 232, 360
    };
    private static final int[] AZTEC_COMPACT_50_DATA_SIZES = {
        36, 102, 176, 280
    };

    private static final int[] AZTEC_OFFSET = {
        66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21,
        19, 17, 15, 13, 10, 8, 6, 4, 2, 0
    };

    private static final int[] AZTEC_COMPACT_OFFSET = {
        6, 4, 2, 0
    };

    /* Initialize AZTEC_MAP */
    static {

        int layer, start, length, n, i;
        int x, y;

        for (x = 0; x < 151; x++) {
            for (y = 0; y < 151; y++) {
                AZTEC_MAP[x][y] = 0;
            }
        }

        for (layer = 1; layer < 33; layer++) {
            start = (112 * (layer - 1)) + (16 * (layer - 1) * (layer - 1)) + 2;
            length = 28 + ((layer - 1) * 4) + (layer * 4);
            /* Top */
            i = 0;
            x = 64 - ((layer - 1) * 2);
            y = 63 - ((layer - 1) * 2);
            for (n = start; n < (start + length); n += 2) {
                AZTEC_MAP[avoidReferenceGrid(x + i)][avoidReferenceGrid(y)] = n;
                AZTEC_MAP[avoidReferenceGrid(x + i)][avoidReferenceGrid(y - 1)] = n + 1;
                i++;
            }
            /* Right */
            i = 0;
            x = 78 + ((layer - 1) * 2);
            y = 64 - ((layer - 1) * 2);
            for (n = start + length; n < (start + (length * 2)); n += 2) {
                AZTEC_MAP[avoidReferenceGrid(x)][avoidReferenceGrid(y + i)] = n;
                AZTEC_MAP[avoidReferenceGrid(x + 1)][avoidReferenceGrid(y + i)] = n + 1;
                i++;
            }
            /* Bottom */
            i = 0;
            x = 77 + ((layer - 1) * 2);
            y = 78 + ((layer - 1) * 2);
            for (n = start + (length * 2); n < (start + (length * 3)); n += 2) {
                AZTEC_MAP[avoidReferenceGrid(x - i)][avoidReferenceGrid(y)] = n;
                AZTEC_MAP[avoidReferenceGrid(x - i)][avoidReferenceGrid(y + 1)] = n + 1;
                i++;
            }
            /* Left */
            i = 0;
            x = 63 - ((layer - 1) * 2);
            y = 77 + ((layer - 1) * 2);
            for (n = start + (length * 3); n < (start + (length * 4)); n += 2) {
                AZTEC_MAP[avoidReferenceGrid(x)][avoidReferenceGrid(y - i)] = n;
                AZTEC_MAP[avoidReferenceGrid(x - 1)][avoidReferenceGrid(y - i)] = n + 1;
                i++;
            }
        }

        /* Central finder pattern */
        for (y = 69; y <= 81; y++) {
            for (x = 69; x <= 81; x++) {
                AZTEC_MAP[x][y] = 1;
            }
        }
        for (y = 70; y <= 80; y++) {
            for (x = 70; x <= 80; x++) {
                AZTEC_MAP[x][y] = 0;
            }
        }
        for (y = 71; y <= 79; y++) {
            for (x = 71; x <= 79; x++) {
                AZTEC_MAP[x][y] = 1;
            }
        }
        for (y = 72; y <= 78; y++) {
            for (x = 72; x <= 78; x++) {
                AZTEC_MAP[x][y] = 0;
            }
        }
        for (y = 73; y <= 77; y++) {
            for (x = 73; x <= 77; x++) {
                AZTEC_MAP[x][y] = 1;
            }
        }
        for (y = 74; y <= 76; y++) {
            for (x = 74; x <= 76; x++) {
                AZTEC_MAP[x][y] = 0;
            }
        }

        /* Guide bars */
        for (y = 11; y < 151; y += 16) {
            for (x = 1; x < 151; x += 2) {
                AZTEC_MAP[x][y] = 1;
                AZTEC_MAP[y][x] = 1;
            }
        }

        /* Descriptor */
        for (i = 0; i < 10; i++) { /* Top */
            AZTEC_MAP[avoidReferenceGrid(66 + i)][avoidReferenceGrid(64)] = 20000 + i;
        }
        for (i = 0; i < 10; i++) { /* Right */
            AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(66 + i)] = 20010 + i;
        }
        for (i = 0; i < 10; i++) { /* Bottom */
            AZTEC_MAP[avoidReferenceGrid(75 - i)][avoidReferenceGrid(77)] = 20020 + i;
        }
        for (i = 0; i < 10; i++) { /* Left */
            AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(75 - i)] = 20030 + i;
        }

        /* Orientation */
        AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(64)] = 1;
        AZTEC_MAP[avoidReferenceGrid(65)][avoidReferenceGrid(64)] = 1;
        AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(65)] = 1;
        AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(64)] = 1;
        AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(65)] = 1;
        AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(76)] = 1;
    }

    private static int avoidReferenceGrid(int input) {
        int output = input;
        if (output > 10) {
            output++;
        }
        if (output > 26) {
            output++;
        }
        if (output > 42) {
            output++;
        }
        if (output > 58) {
            output++;
        }
        if (output > 74) {
            output++;
        }
        if (output > 90) {
            output++;
        }
        if (output > 106) {
            output++;
        }
        if (output > 122) {
            output++;
        }
        if (output > 138) {
            output++;
        }
        return output;
    }

    private int preferredSize = 0;
    private int preferredEccLevel = 2;
    private String structuredAppendMessageId;
    private int structuredAppendPosition = 1;
    private int structuredAppendTotal = 1;

    /**
     * <p>Sets a preferred symbol size. This value may be ignored if data string is
     * too large to fit in the specified symbol size. Values correspond to symbol
     * sizes as shown in the following table:
     *
     * <table>
     * <tbody>
     * <tr><th>Input</th><th>Symbol Size</th><th>Input</th><th>Symbol Size</th></tr>
     * <tr><td>1    </td><td>15 x 15    </td><td>19   </td><td>79 x 79    </td></tr>
     * <tr><td>2    </td><td>19 x 19    </td><td>20   </td><td>83 x 83    </td></tr>
     * <tr><td>3    </td><td>23 x 23    </td><td>21   </td><td>87 x 87    </td></tr>
     * <tr><td>4    </td><td>27 x 27    </td><td>22   </td><td>91 x 91    </td></tr>
     * <tr><td>5    </td><td>19 x 19    </td><td>23   </td><td>95 x 95    </td></tr>
     * <tr><td>6    </td><td>23 x 23    </td><td>24   </td><td>101 x 101  </td></tr>
     * <tr><td>7    </td><td>27 x 27    </td><td>25   </td><td>105 x 105  </td></tr>
     * <tr><td>8    </td><td>31 x 31    </td><td>26   </td><td>109 x 109  </td></tr>
     * <tr><td>9    </td><td>37 x 37    </td><td>27   </td><td>113 x 113  </td></tr>
     * <tr><td>10   </td><td>41 x 41    </td><td>28   </td><td>117 x 117  </td></tr>
     * <tr><td>11   </td><td>45 x 45    </td><td>29   </td><td>121 x 121  </td></tr>
     * <tr><td>12   </td><td>49 x 49    </td><td>30   </td><td>125 x 125  </td></tr>
     * <tr><td>13   </td><td>53 x 53    </td><td>31   </td><td>131 x 131  </td></tr>
     * <tr><td>14   </td><td>57 x 57    </td><td>32   </td><td>135 x 135  </td></tr>
     * <tr><td>15   </td><td>61 x 61    </td><td>33   </td><td>139 x 139  </td></tr>
     * <tr><td>16   </td><td>67 x 67    </td><td>34   </td><td>143 x 143  </td></tr>
     * <tr><td>17   </td><td>71 x 71    </td><td>35   </td><td>147 x 147  </td></tr>
     * <tr><td>18   </td><td>75 x 75    </td><td>36   </td><td>151 x 151  </td></tr>
     * </tbody>
     * </table>
     *
     * <p> Note that sizes 1 to 4 are the "compact" Aztec Code symbols; sizes 5 to 36
     * are the "full-range" Aztec Code symbols.
     *
     * @param size an integer in the range 1 - 36
     */
    public void setPreferredSize(int size) {
        if (size < 1 || size > 36) {
            throw new IllegalArgumentException("Invalid size: " + size);
        }
        preferredSize = size;
    }

    /**
     * Returns the preferred symbol size.
     *
     * @return the preferred symbol size
     */
    public int getPreferredSize() {
        return preferredSize;
    }

    /**
     * Sets the preferred minimum amount of symbol space dedicated to error
     * correction. This value will be ignored if a symbol size has been set by
     * <code>setPreferredSize</code>. Valid options are:
     *
     * <table>
     * <tbody>
     * <tr><th>Mode</th><th>Error Correction Capacity</th></tr>
     * <tr><td>1   </td><td>&gt; 10% + 3 codewords    </td></tr>
     * <tr><td>2   </td><td>&gt; 23% + 3 codewords    </td></tr>
     * <tr><td>3   </td><td>&gt; 36% + 3 codewords    </td></tr>
     * <tr><td>4   </td><td>&gt; 50% + 3 codewords    </td></tr>
     * </tbody>
     * </table>
     *
     * @param eccLevel an integer in the range 1 - 4
     */
    public void setPreferredEccLevel(int eccLevel) {
        if (eccLevel < 1 || eccLevel > 4) {
            throw new IllegalArgumentException("Invalid ECC level: " + eccLevel);
        }
        preferredEccLevel = eccLevel;
    }

    /**
     * Returns the preferred error correction level.
     *
     * @return the preferred error correction level
     */
    public int getPreferredEccLevel() {
        return preferredEccLevel;
    }

    /**
     * If this Aztec Code symbol is part of a series of Aztec Code symbols appended in a structured
     * format, this method sets the position of this symbol in the series. Valid values are 1 through
     * 26 inclusive.
     *
     * @param position the position of this Aztec Code symbol in the structured append series
     */
    public void setStructuredAppendPosition(int position) {
        if (position < 1 || position > 26) {
            throw new IllegalArgumentException("Invalid Aztec Code structured append position: " + position);
        }
        this.structuredAppendPosition = position;
    }

    /**
     * Returns the position of this Aztec Code symbol in a series of symbols using structured append.
     * If this symbol is not part of such a series, this method will return <code>1</code>.
     *
     * @return the position of this Aztec Code symbol in a series of symbols using structured append
     */
    public int getStructuredAppendPosition() {
        return structuredAppendPosition;
    }

    /**
     * If this Aztec Code symbol is part of a series of Aztec Code symbols appended in a structured
     * format, this method sets the total number of symbols in the series. Valid values are
     * 1 through 26 inclusive. A value of 1 indicates that this symbol is not part of a structured
     * append series.
     *
     * @param total the total number of Aztec Code symbols in the structured append series
     */
    public void setStructuredAppendTotal(int total) {
        if (total < 1 || total > 26) {
            throw new IllegalArgumentException("Invalid Aztec Code structured append total: " + total);
        }
        this.structuredAppendTotal = total;
    }

    /**
     * Returns the size of the series of Aztec Code symbols using structured append that this symbol
     * is part of. If this symbol is not part of a structured append series, this method will return
     * <code>1</code>.
     *
     * @return size of the series that this symbol is part of
     */
    public int getStructuredAppendTotal() {
        return structuredAppendTotal;
    }

    /**
     * If this Aztec Code symbol is part of a series of Aztec Code symbols appended in a structured format,
     * this method sets the unique message ID for the series. Values may not contain spaces and must contain
     * only printable ASCII characters. Message IDs are optional.
     *
     * @param messageId the unique message ID for the series that this symbol is part of
     */
    public void setStructuredAppendMessageId(String messageId) {
        if (messageId != null && !messageId.matches("^[\\x21-\\x7F]+$")) {
            throw new IllegalArgumentException("Invalid Aztec Code structured append message ID: " + messageId);
        }
        this.structuredAppendMessageId = messageId;
    }

    /**
     * Returns the unique message ID of the series of Aztec Code symbols using structured append that this
     * symbol is part of. If this symbol is not part of a structured append series, this method will return
     * <code>null</code>.
     *
     * @return the unique message ID for the series that this symbol is part of
     */
    public String getStructuredAppendMessageId() {
        return structuredAppendMessageId;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {

        int layers;
        boolean compact;
        StringBuilder adjustedString;

        if (inputDataType == DataType.GS1 && readerInit) {
            throw new OkapiException("Cannot encode in GS1 and Reader Initialisation mode at the same time");
        }

        eciProcess(); // Get ECI mode

        /* Optional structured append (Section 8 of spec) */
        /* ML + UL start flag handled later, not part of data */
        if (structuredAppendTotal != 1) {
            StringBuilder prefix = new StringBuilder();
            if (structuredAppendMessageId != null) {
                prefix.append(' ').append(structuredAppendMessageId).append(' ');
            }
            prefix.append((char) (structuredAppendPosition + 64)); // 1-26 as A-Z
            prefix.append((char) (structuredAppendTotal + 64)); // 1-26 as A-Z
            int[] prefixArray = toBytes(prefix.toString(), US_ASCII);
            inputData = insertArray(inputData, 0, prefixArray);
        }

        String binaryString = generateAztecBinary();
        int dataLength = binaryString.length();

        if (preferredSize == 0) {

            /* The size of the symbol can be determined by Okapi */

            int dataMaxSize = 0;
            int compLoop = (readerInit ? 1 : 4);

            do {
                /* Decide what size symbol to use - the smallest that fits the data */

                int[] dataSizes;
                int[] compactDataSizes;

                switch (preferredEccLevel) {
                    /* For each level of error correction work out the smallest symbol which the data will fit in */
                    case 1:
                        dataSizes = AZTEC_10_DATA_SIZES;
                        compactDataSizes = AZTEC_COMPACT_10_DATA_SIZES;
                        break;
                    case 2:
                        dataSizes = AZTEC_23_DATA_SIZES;
                        compactDataSizes = AZTEC_COMPACT_23_DATA_SIZES;
                        break;
                    case 3:
                        dataSizes = AZTEC_36_DATA_SIZES;
                        compactDataSizes = AZTEC_COMPACT_36_DATA_SIZES;
                        break;
                    case 4:
                        dataSizes = AZTEC_50_DATA_SIZES;
                        compactDataSizes = AZTEC_COMPACT_50_DATA_SIZES;
                        break;
                    default:
                        throw new OkapiException("Unrecognized ECC level: " + preferredEccLevel);
                }

                layers = 0;
                compact = false;

                for (int i = 32; i > 0; i--) {
                    if (dataLength < dataSizes[i - 1]) {
                        layers = i;
                        compact = false;
                        dataMaxSize = dataSizes[i - 1];
                    }
                }

                for (int i = compLoop; i > 0; i--) {
                    if (dataLength < compactDataSizes[i - 1]) {
                        layers = i;
                        compact = true;
                        dataMaxSize = compactDataSizes[i - 1];
                    }
                }

                if (layers == 0) {
                    /* Couldn't find a symbol which fits the data */
                    throw new OkapiException("Input too long (too many bits for selected ECC)");
                }

                adjustedString = adjustBinaryString(binaryString, compact, layers);
                dataLength = adjustedString.length();

            } while (dataLength > dataMaxSize);
            /* This loop will only repeat on the rare occasions when the rule about not having all 1s or all 0s
             means that the binary string has had to be lengthened beyond the maximum number of bits that can
             be encoded in a symbol of the selected size */

        } else {

            /* The size of the symbol has been specified by the user */

            if (preferredSize >= 1 && preferredSize <= 4) {
                compact = true;
                layers = preferredSize;
            } else {
                compact = false;
                layers = preferredSize - 4;
            }

            adjustedString = adjustBinaryString(binaryString, compact, layers);

            /* Check if the data actually fits into the selected symbol size */
            int codewordSize = getCodewordSize(layers);
            int[] sizes = (compact ? AZTEC_COMPACT_SIZES : AZTEC_SIZES);
            int dataMaxSize = codewordSize * (sizes[layers - 1] - 3);
            if (adjustedString.length() > dataMaxSize) {
                throw new OkapiException("Data too long for specified Aztec Code symbol size");
            }
        }

        if (readerInit && compact && layers > 1) {
            throw new OkapiException("Symbol is too large for reader initialization");
        }

        if (readerInit && layers > 22) {
            throw new OkapiException("Symbol is too large for reader initialization");
        }

        int codewordSize = getCodewordSize(layers);
        int dataBlocks = adjustedString.length() / codewordSize;

        int eccBlocks;
        if (compact) {
            eccBlocks = AZTEC_COMPACT_SIZES[layers - 1] - dataBlocks;
        } else {
            eccBlocks = AZTEC_SIZES[layers - 1] - dataBlocks;
        }

        infoLine("Compact Mode: " + compact);
        infoLine("Layers: " + layers);
        infoLine("Codeword Length: " + codewordSize + " bits");
        infoLine("Data Codewords: " + dataBlocks);
        infoLine("ECC Codewords: " + eccBlocks);

        /* Add ECC data to the adjusted string */
        addErrorCorrection(adjustedString, codewordSize, dataBlocks, eccBlocks);

        /* Invert the data so that actual data is on the outside and reed-solomon on the inside */
        for (int i = 0; i < adjustedString.length() / 2; i++) {
            int mirror = adjustedString.length() - i - 1;
            char c = adjustedString.charAt(i);
            adjustedString.setCharAt(i, adjustedString.charAt(mirror));
            adjustedString.setCharAt(mirror, c);
        }

        /* Create the descriptor / mode message */
        String descriptor = createDescriptor(compact, layers, dataBlocks);

        /* Plot all of the data into the symbol in pre-defined spiral pattern */
        if (compact) {

            readable = "";
            row_count = 27 - (2 * AZTEC_COMPACT_OFFSET[layers - 1]);
            row_height = new int[row_count];
            row_height[0] = -1;
            pattern = new String[row_count];
            for (int y = AZTEC_COMPACT_OFFSET[layers - 1]; y < (27 - AZTEC_COMPACT_OFFSET[layers - 1]); y++) {
                StringBuilder bin = new StringBuilder(27);
                for (int x = AZTEC_COMPACT_OFFSET[layers - 1]; x < (27 - AZTEC_COMPACT_OFFSET[layers - 1]); x++) {
                    int j = COMPACT_AZTEC_MAP[(y * 27) + x];
                    if (j == 0) {
                        bin.append('0');
                    }
                    if (j == 1) {
                        bin.append('1');
                    }
                    if (j >= 2) {
                        if (j - 2 < adjustedString.length()) {
                            bin.append(adjustedString.charAt(j - 2));
                        } else {
                            if (j >= 2000) {
                                bin.append(descriptor.charAt(j - 2000));
                            } else {
                                bin.append('0');
                            }
                        }
                    }
                }
                row_height[y - AZTEC_COMPACT_OFFSET[layers - 1]] = 1;
                pattern[y - AZTEC_COMPACT_OFFSET[layers - 1]] = bin2pat(bin);
            }

        } else {

            readable = "";
            row_count = 151 - (2 * AZTEC_OFFSET[layers - 1]);
            row_height = new int[row_count];
            row_height[0] = -1;
            pattern = new String[row_count];
            for (int y = AZTEC_OFFSET[layers - 1]; y < (151 - AZTEC_OFFSET[layers - 1]); y++) {
                StringBuilder bin = new StringBuilder(151);
                for (int x = AZTEC_OFFSET[layers - 1]; x < (151 - AZTEC_OFFSET[layers - 1]); x++) {
                    int j = AZTEC_MAP[x][y];
                    if (j == 1) {
                        bin.append('1');
                    }
                    if (j == 0) {
                        bin.append('0');
                    }
                    if (j >= 2) {
                        if (j - 2 < adjustedString.length()) {
                            bin.append(adjustedString.charAt(j - 2));
                        } else {
                            if (j >= 20000) {
                                bin.append(descriptor.charAt(j - 20000));
                            } else {
                                bin.append('0');
                            }
                        }
                    }
                }
                row_height[y - AZTEC_OFFSET[layers - 1]] = 1;
                pattern[y - AZTEC_OFFSET[layers - 1]] = bin2pat(bin);
            }
        }
    }

    private String generateAztecBinary() {

        /* Encode input data into a binary string */
        int i, j, k, bytes;
        int curtable, newtable, lasttable, chartype, maplength, blocks;
        int[] charmap = new int[2 * inputData.length];
        int[] typemap = new int[2 * inputData.length];
        int[] blockType = new int[inputData.length + 1];
        int[] blockLength = new int[inputData.length + 1];

        /* Lookup input string in encoding table */
        maplength = 0;

        /* Add FNC1 to beginning of GS1 messages */
        if (inputDataType == DataType.GS1) {
            charmap[maplength] = 0; // FLG
            typemap[maplength++] = 8; // PUNC
            charmap[maplength] = 400; // (0)
            typemap[maplength++] = 8; // PUNC
        }

        if (eciMode != 3) {
            int flagNumber;

            charmap[maplength] = 0; // FLG
            typemap[maplength++] = 8; // PUNC

            flagNumber = 6;

            if (eciMode < 100000) {
                flagNumber = 5;
            }

            if (eciMode < 10000) {
                flagNumber = 4;
            }

            if (eciMode < 1000) {
                flagNumber = 3;
            }

            if (eciMode < 100) {
                flagNumber = 2;
            }

            if (eciMode < 10) {
                flagNumber = 1;
            }

            charmap[maplength] = 400 + flagNumber;
            typemap[maplength++] = 8; // PUNC
        }

        for (i = 0; i < inputData.length; i++) {
            if (inputData[i] == FNC1) {
                /* FNC1 represented by FLG(0) */
                charmap[maplength] = 0; // FLG
                typemap[maplength++] = 8; // PUNC
                charmap[maplength] = 400; // (0)
                typemap[maplength++] = 8; // PUNC
            } else {
                if ((inputData[i] > 0x7F) || (inputData[i] == 0x00)) {
                    charmap[maplength] = inputData[i];
                    typemap[maplength++] = 32; //BINARY
                } else {
                    charmap[maplength] = AZTEC_SYMBOL_CHAR[inputData[i]];
                    typemap[maplength++] = AZTEC_CODE_SET[inputData[i]];
                }
            }
        }

        /* Look for double character encoding possibilities */
        for (i = 0; i < (maplength - 1); i++) {
            if (((charmap[i] == 300) && (charmap[i + 1] == 11)) && ((typemap[i] == 12) && (typemap[i + 1] == 4))) {
                /* CR LF combination */
                charmap[i] = 2;
                typemap[i] = 8; // PUNC
                if ((i + 1) != maplength) {
                    for (j = i + 1; j < maplength; j++) {
                        charmap[j] = charmap[j + 1];
                        typemap[j] = typemap[j + 1];
                    }
                }
                maplength--;
            }

            if (((charmap[i] == 302) && (charmap[i + 1] == 1)) && ((typemap[i] == 24) && (typemap[i + 1] == 23))) {
                /* . SP combination */
                charmap[i] = 3;
                typemap[i] = 8; // PUNC;
                if ((i + 1) != maplength) {
                    for (j = i + 1; j < maplength; j++) {
                        charmap[j] = charmap[j + 1];
                        typemap[j] = typemap[j + 1];
                    }
                }
                maplength--;
            }

            if (((charmap[i] == 301) && (charmap[i + 1] == 1)) && ((typemap[i] == 24) && (typemap[i + 1] == 23))) {
                /* , SP combination */
                charmap[i] = 4;
                typemap[i] = 8; //PUNC;
                if ((i + 1) != maplength) {
                    for (j = i + 1; j < maplength; j++) {
                        charmap[j] = charmap[j + 1];
                        typemap[j] = typemap[j + 1];
                    }
                }
                maplength--;
            }

            if (((charmap[i] == 21) && (charmap[i + 1] == 1)) && ((typemap[i] == 8) && (typemap[i + 1] == 23))) {
                /* : SP combination */
                charmap[i] = 5;
                typemap[i] = 8; //PUNC;
                if ((i + 1) != maplength) {
                    for (j = i + 1; j < maplength; j++) {
                        charmap[j] = charmap[j + 1];
                        typemap[j] = typemap[j + 1];
                    }
                }
                maplength--;
            }
        }

        /* look for blocks of characters which use the same table */
        blocks = 0;
        for (i = 0; i < maplength; i++) {
            if (i > 0 && typemap[i] == typemap[i - 1]) {
                blockLength[blocks - 1]++;
            } else {
                blocks++;
                blockType[blocks - 1] = typemap[i];
                blockLength[blocks - 1] = 1;
            }
        }

        if ((blockType[0] & 1) != 0) {
            blockType[0] = 1;
        }
        if ((blockType[0] & 2) != 0) {
            blockType[0] = 2;
        }
        if ((blockType[0] & 4) != 0) {
            blockType[0] = 4;
        }
        if ((blockType[0] & 8) != 0) {
            blockType[0] = 8;
        }

        if (blocks > 1) {

            /* look for adjacent blocks which can use the same table (left to right search) */
            for (i = 1; i < blocks; i++) {
                if ((blockType[i] & blockType[i - 1]) != 0) {
                    blockType[i] = (blockType[i] & blockType[i - 1]);
                }
            }

            if ((blockType[blocks - 1] & 1) != 0) {
                blockType[blocks - 1] = 1;
            }
            if ((blockType[blocks - 1] & 2) != 0) {
                blockType[blocks - 1] = 2;
            }
            if ((blockType[blocks - 1] & 4) != 0) {
                blockType[blocks - 1] = 4;
            }
            if ((blockType[blocks - 1] & 8) != 0) {
                blockType[blocks - 1] = 8;
            }

            /* look for adjacent blocks which can use the same table (right to left search) */
            for (i = blocks - 2; i > 0; i--) {
                if ((blockType[i] & blockType[i + 1]) != 0) {
                    blockType[i] = (blockType[i] & blockType[i + 1]);
                }
            }

            /* determine the encoding table for characters which do not fit with adjacent blocks */
            for (i = 1; i < blocks; i++) {
                if ((blockType[i] & 8) != 0) {
                    blockType[i] = 8;
                }
                if ((blockType[i] & 4) != 0) {
                    blockType[i] = 4;
                }
                if ((blockType[i] & 2) != 0) {
                    blockType[i] = 2;
                }
                if ((blockType[i] & 1) != 0) {
                    blockType[i] = 1;
                }
            }

            /* if less than 4 characters are preceded and followed by binary blocks
               then it is more efficient to also encode these in binary
            */

//            for (i = 1; i < blocks - 1; i++) {
//                if ((blockType[i - 1] == 32) && (blockLength[i] < 4)) {
//                    int nonBinaryLength = blockLength[i];
//                    for (int l = i; ((l < blocks) && (blockType[l] != 32)); l++) {
//                        nonBinaryLength += blockLength[l];
//                    }
//                    if (nonBinaryLength < 4) {
//                        blockType[i] = 32;
//                    }
//                }
//            }

            /* Combine blocks of the same type */
            i = 0;
            do {
                if (blockType[i] == blockType[i + 1]) {
                    blockLength[i] += blockLength[i + 1];
                    for (j = i + 1; j < blocks - 1; j++) {
                        blockType[j] = blockType[j + 1];
                        blockLength[j] = blockLength[j + 1];
                    }
                    blocks--;
                } else {
                    i++;
                }
            } while (i < blocks - 1);
        }

        /* Put the adjusted block data back into typemap */
        j = 0;
        for (i = 0; i < blocks; i++) {
            if ((blockLength[i] < 3) && (blockType[i] != 32)) { /* Shift character(s) needed */

                for (k = 0; k < blockLength[i]; k++) {
                    typemap[j + k] = blockType[i] + 64;
                }
            } else { /* Latch character (or byte mode) needed */

                for (k = 0; k < blockLength[i]; k++) {
                    typemap[j + k] = blockType[i];
                }
            }
            j += blockLength[i];
        }

        /* Don't shift an initial capital letter */
        if (maplength > 0 && typemap[0] == 65) {
            typemap[0] = 1;
        }

        /* Problem characters (those that appear in different tables with different values) can now be resolved into their tables */
        for (i = 0; i < maplength; i++) {
            if ((charmap[i] >= 300) && (charmap[i] < 400)) {
                curtable = typemap[i];
                if (curtable > 64) {
                    curtable -= 64;
                }
                switch (charmap[i]) {
                    case 300:
                        /* Carriage Return */
                        switch (curtable) {
                            case 8:
                                charmap[i] = 1;
                                break; // PUNC
                            case 4:
                                charmap[i] = 14;
                                break; // PUNC
                        }
                        break;
                    case 301:
                        /* Comma */
                        switch (curtable) {
                            case 8:
                                charmap[i] = 17;
                                break; // PUNC
                            case 16:
                                charmap[i] = 12;
                                break; // DIGIT
                        }
                        break;
                    case 302:
                        /* Full Stop */
                        switch (curtable) {
                            case 8:
                                charmap[i] = 19;
                                break; // PUNC
                            case 16:
                                charmap[i] = 13;
                                break; // DIGIT
                        }
                        break;
                }
            }
        }

        StringBuilder binaryString = new StringBuilder();
        info("Encoding: ");
        curtable = 1; /* start with 1 table */
        lasttable = 1;

        /* Optional structured append start flag (Section 8 of spec) */
        if (structuredAppendTotal != 1) {
            binaryString.append(PENTBIT[29]);
            info("ML ");
            binaryString.append(PENTBIT[29]);
            info("UL ");
        }

        for (i = 0; i < maplength; i++) {
            newtable = curtable;
            if ((typemap[i] != curtable) && (charmap[i] < 400)) {
                /* Change table */
                if (curtable == 32) {
                    /* If ending binary mode the current table is the same as when entering binary mode */
                    curtable = lasttable;
                    newtable = lasttable;
                }
                if (typemap[i] > 64) {
                    /* Shift character */
                    switch (typemap[i]) {
                        case (64 + 1):
                            /* To UPPER */
                            switch (curtable) {
                                case 2:
                                    /* US */
                                    binaryString.append(PENTBIT[28]);
                                    info("US ");
                                    break;
                                case 4:
                                    /* UL */
                                    binaryString.append(PENTBIT[29]);
                                    info("UL ");
                                    newtable = 1;
                                    break;
                                case 8:
                                    /* UL */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    newtable = 1;
                                    break;
                                case 16:
                                    /* US */
                                    binaryString.append(QUADBIT[15]);
                                    info("US ");
                                    break;
                            }
                            break;
                        case (64 + 2):
                            /* To LOWER */
                            switch (curtable) {
                                case 1:
                                    /* LL */
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                                case 4:
                                    /* LL */
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                                case 8:
                                    /* UL LL */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                                case 16:
                                    /* UL LL */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                            }
                            break;
                        case (64 + 4):
                            /* To MIXED */
                            switch (curtable) {
                                case 1:
                                    /* ML */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                                case 2:
                                    /* ML */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                                case 8:
                                    /* UL ML */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                                case 16:
                                    /* UL ML */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                            }
                            break;
                        case (64 + 8):
                            /* To PUNC */
                            switch (curtable) {
                                case 1:
                                    /* PS */
                                    binaryString.append(PENTBIT[0]);
                                    info("PS ");
                                    break;
                                case 2:
                                    /* PS */
                                    binaryString.append(PENTBIT[0]);
                                    info("PS ");
                                    break;
                                case 4:
                                    /* PS */
                                    binaryString.append(PENTBIT[0]);
                                    info("PS ");
                                    break;
                                case 16:
                                    /* PS */
                                    binaryString.append(QUADBIT[0]);
                                    info("PS ");
                                    break;
                            }
                            break;
                        case (64 + 16):
                            /* To DIGIT */
                            switch (curtable) {
                                case 1:
                                    /* DL */
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                                case 2:
                                    /* DL */
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                                case 4:
                                    /* UL DL */
                                    binaryString.append(PENTBIT[29]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                                case 8:
                                    /* UL DL */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                            }
                            break;
                    }
                } else {
                    /* Latch character */
                    switch (typemap[i]) {
                        case 1:
                            /* To UPPER */
                            switch (curtable) {
                                case 2:
                                    /* ML UL */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    binaryString.append(PENTBIT[29]);
                                    info("UL ");
                                    newtable = 1;
                                    break;
                                case 4:
                                    /* UL */
                                    binaryString.append(PENTBIT[29]);
                                    info("UL ");
                                    newtable = 1;
                                    break;
                                case 8:
                                    /* UL */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    newtable = 1;
                                    break;
                                case 16:
                                    /* UL */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    newtable = 1;
                                    break;
                            }
                            break;
                        case 2:
                            /* To LOWER */
                            switch (curtable) {
                                case 1:
                                    /* LL */
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                                case 4:
                                    /* LL */
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                                case 8:
                                    /* UL LL */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                                case 16:
                                    /* UL LL */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[28]);
                                    info("LL ");
                                    newtable = 2;
                                    break;
                            }
                            break;
                        case 4:
                            /* To MIXED */
                            switch (curtable) {
                                case 1:
                                    /* ML */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                                case 2:
                                    /* ML */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                                case 8:
                                    /* UL ML */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                                case 16:
                                    /* UL ML */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    newtable = 4;
                                    break;
                            }
                            break;
                        case 8:
                            /* To PUNC */
                            switch (curtable) {
                                case 1:
                                    /* ML PL */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    binaryString.append(PENTBIT[30]);
                                    info("PL ");
                                    newtable = 8;
                                    break;
                                case 2:
                                    /* ML PL */
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    binaryString.append(PENTBIT[30]);
                                    info("PL ");
                                    newtable = 8;
                                    break;
                                case 4:
                                    /* PL */
                                    binaryString.append(PENTBIT[30]);
                                    info("PL ");
                                    newtable = 8;
                                    break;
                                case 16:
                                    /* UL ML PL */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[29]);
                                    info("ML ");
                                    binaryString.append(PENTBIT[30]);
                                    info("PL ");
                                    newtable = 8;
                                    break;
                            }
                            break;
                        case 16:
                            /* To DIGIT */
                            switch (curtable) {
                                case 1:
                                    /* DL */
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                                case 2:
                                    /* DL */
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                                case 4:
                                    /* UL DL */
                                    binaryString.append(PENTBIT[29]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                                case 8:
                                    /* UL DL */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[30]);
                                    info("DL ");
                                    newtable = 16;
                                    break;
                            }
                            break;
                        case 32:
                            /* To BINARY */
                            lasttable = curtable;
                            switch (curtable) {
                                case 1:
                                    /* BS */
                                    binaryString.append(PENTBIT[31]);
                                    info("BS ");
                                    newtable = 32;
                                    break;
                                case 2:
                                    /* BS */
                                    binaryString.append(PENTBIT[31]);
                                    info("BS ");
                                    newtable = 32;
                                    break;
                                case 4:
                                    /* BS */
                                    binaryString.append(PENTBIT[31]);
                                    info("BS ");
                                    newtable = 32;
                                    break;
                                case 8:
                                    /* UL BS */
                                    binaryString.append(PENTBIT[31]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[31]);
                                    info("BS ");
                                    lasttable = 1;
                                    newtable = 32;
                                    break;
                                case 16:
                                    /* UL BS */
                                    binaryString.append(QUADBIT[14]);
                                    info("UL ");
                                    binaryString.append(PENTBIT[31]);
                                    info("BS ");
                                    lasttable = 1;
                                    newtable = 32;
                                    break;
                            }

                            bytes = 0;
                            do {
                                bytes++;
                            } while (typemap[i + (bytes - 1)] == 32);
                            bytes--;

                            if (bytes > 2079) {
                                throw new OkapiException("Input too long");
                            }

                            if (bytes > 31) {
                                /* Put 00000 followed by 11-bit number of bytes less 31 */
                                binaryString.append("00000");
                                for (int weight = 0x400; weight > 0; weight = weight >> 1) {
                                    if (((bytes - 31) & weight) != 0) {
                                        binaryString.append('1');
                                    } else {
                                        binaryString.append('0');
                                    }
                                }
                            } else {
                                /* Put 5-bit number of bytes */
                                for (int weight = 0x10; weight > 0; weight = weight >> 1) {
                                    if ((bytes & weight) != 0) {
                                        binaryString.append('1');
                                    } else {
                                        binaryString.append('0');
                                    }
                                }
                            }

                            break;
                    }
                }
            }
            /* Add data to the binary string */
            curtable = newtable;
            chartype = typemap[i];
            if (chartype > 64) {
                chartype -= 64;
            }
            switch (chartype) {
                case 1:
                case 2:
                case 4:
                case 8:
                    if (charmap[i] >= 400) {
                        info("FLG(" + (charmap[i] - 400) + ") ");
                        binaryString.append(TRIBIT[charmap[i] - 400]);
                        if (charmap[i] != 400) {
                            /* ECI */
                            binaryString.append(eciToBinary());
                        }
                    } else {
                        binaryString.append(PENTBIT[charmap[i]]);
                        infoSpace(charmap[i]);
                    }
                    break;
                case 16:
                    binaryString.append(QUADBIT[charmap[i]]);
                    infoSpace(charmap[i]);
                    break;
                case 32:
                    for (int weight = 0x80; weight > 0; weight = weight >> 1) {
                        if ((charmap[i] & weight) != 0) {
                            binaryString.append('1');
                        } else {
                            binaryString.append('0');
                        }
                    }
                    infoSpace(charmap[i]);
                    break;
            }
        }

        infoLine();

        return binaryString.toString();
    }

    /** Adjusts bit stream so that no codewords are all 0s or all 1s, per Section 7.3.1.2 */
    private StringBuilder adjustBinaryString(String binaryString, boolean compact, int layers) {

        StringBuilder adjustedString = new StringBuilder();
        int codewordSize = getCodewordSize(layers);
        int ones = 0;

        /* Insert dummy digits needed to prevent codewords of all 0s or all 1s */
        for (int i = 0; i < binaryString.length(); i++) {
            if ((adjustedString.length() + 1) % codewordSize == 0) {
                if (ones == codewordSize - 1) {
                    // codeword of B-1 1s, add dummy 0
                    adjustedString.append('0');
                    i--;
                } else if (ones == 0) {
                    // codeword of B-1 0s, add dummy 1
                    adjustedString.append('1');
                    i--;
                } else {
                    // no dummy value needed
                    adjustedString.append(binaryString.charAt(i));
                }
                ones = 0;
            } else {
                adjustedString.append(binaryString.charAt(i));
                if (binaryString.charAt(i) == '1') {
                    ones++;
                }
            }
        }

        /* Add padding */
        int adjustedLength = adjustedString.length();
        int remainder = adjustedLength % codewordSize;
        int padBits = codewordSize - remainder;
        if (padBits == codewordSize) {
            padBits = 0;
        }
        for (int i = 0; i < padBits; i++) {
            adjustedString.append('1');
        }
        adjustedLength = adjustedString.length();

        /* Make sure padding didn't create an invalid (all 1s) codeword */
        ones = 0;
        for (int i = adjustedLength - codewordSize; i < adjustedLength && i >= 0; i++) {
            if (adjustedString.charAt(i) == '1') {
                ones++;
            }
        }
        if (ones == codewordSize) {
            adjustedString.setCharAt(adjustedLength - 1, '0');
        }

        /* Log the codewords */
        info("Codewords: ");
        for (int i = 0; i < (adjustedLength / codewordSize); i++) {
            int l = 0, m = (1 << (codewordSize - 1));
            for (int j = 0; j < codewordSize; j++) {
                if (adjustedString.charAt((i * codewordSize) + j) == '1') {
                    l += m;
                }
                m = m >> 1;
            }
            infoSpace(l);
        }
        infoLine();

        /* Return the adjusted bit string */
        return adjustedString;
    }

    private String eciToBinary() {
        String eciNumber = Integer.toString(eciMode);
        StringBuilder binary = new StringBuilder(4 * eciNumber.length());
        for (int i = 0; i < eciNumber.length(); i++) {
            binary.append(QUADBIT[(eciNumber.charAt(i) - '0') + 2]);
            infoSpace(eciNumber.charAt(i));
        }
        return binary.toString();
    }

    /** Creates the descriptor / mode message, per Section 7.2 */
    private String createDescriptor(boolean compact, int layers, int dataBlocks) {

        StringBuilder descriptor = new StringBuilder();
        int descDataSize;

        if (compact) {
            /* The first 2 bits represent the number of layers minus 1 */
            if (((layers - 1) & 0x02) != 0) {
                descriptor.append('1');
            } else {
                descriptor.append('0');
            }
            if (((layers - 1) & 0x01) != 0) {
                descriptor.append('1');
            } else {
                descriptor.append('0');
            }
            /* The next 6 bits represent the number of data blocks minus 1 */
            if (readerInit) {
                descriptor.append('1');
            } else {
                if (((dataBlocks - 1) & 0x20) != 0) {
                    descriptor.append('1');
                } else {
                    descriptor.append('0');
                }
            }
            for (int i = 0x10; i > 0; i = i >> 1) {
                if (((dataBlocks - 1) & i) != 0) {
                    descriptor.append('1');
                } else {
                    descriptor.append('0');
                }
            }
            descDataSize = 2;
        } else {
            /* The first 5 bits represent the number of layers minus 1 */
            for (int i = 0x10; i > 0; i = i >> 1) {
                if (((layers - 1) & i) != 0) {
                    descriptor.append('1');
                } else {
                    descriptor.append('0');
                }
            }

            /* The next 11 bits represent the number of data blocks minus 1 */
            if (readerInit) {
                descriptor.append('1');
            } else {
                if (((dataBlocks - 1) & 0x400) != 0) {
                    descriptor.append('1');
                } else {
                    descriptor.append('0');
                }
            }
            for (int i = 0x200; i > 0; i = i >> 1) {
                if (((dataBlocks - 1) & i) != 0) {
                    descriptor.append('1');
                } else {
                    descriptor.append('0');
                }
            }
            descDataSize = 4;
        }

        infoLine("Mode Message: " + descriptor);

        /* Split into 4-bit codewords */
        int[] desc_data = new int[descDataSize];
        for (int i = 0; i < descDataSize; i++) {
            for (int weight = 0; weight < 4; weight++) {
                if (descriptor.charAt((i * 4) + weight) == '1') {
                    desc_data[i] += (8 >> weight);
                }
            }
        }

        /* Add Reed-Solomon error correction with Galois Field GF(16) and prime modulus x^4 + x + 1 (Section 7.2.3) */
        ReedSolomon rs = new ReedSolomon();
        rs.init_gf(0x13);
        if (compact) {
            rs.init_code(5, 1);
            rs.encode(2, desc_data);
            int[] desc_ecc = new int[6];
            for (int i = 0; i < 5; i++) {
                desc_ecc[i] = rs.getResult(i);
            }
            for (int i = 0; i < 5; i++) {
                for (int weight = 0x08; weight > 0; weight = weight >> 1) {
                    if ((desc_ecc[4 - i] & weight) != 0) {
                        descriptor.append('1');
                    } else {
                        descriptor.append('0');
                    }
                }
            }
        } else {
            rs.init_code(6, 1);
            rs.encode(4, desc_data);
            int[] desc_ecc = new int[6];
            for (int i = 0; i < 6; i++) {
                desc_ecc[i] = rs.getResult(i);
            }
            for (int i = 0; i < 6; i++) {
                for (int weight = 0x08; weight > 0; weight = weight >> 1) {
                    if ((desc_ecc[5 - i] & weight) != 0) {
                        descriptor.append('1');
                    } else {
                        descriptor.append('0');
                    }
                }
            }
        }

        return descriptor.toString();
    }

    /** Adds error correction data to the specified binary string, which already contains the primary data */
    private void addErrorCorrection(StringBuilder adjustedString, int codewordSize, int dataBlocks, int eccBlocks) {

        int x, poly, startWeight;

        /* Split into codewords and calculate Reed-Solomon error correction codes */
        switch (codewordSize) {
            case 6:
                x = 32;
                poly = 0x43;
                startWeight = 0x20;
                break;
            case 8:
                x = 128;
                poly = 0x12d;
                startWeight = 0x80;
                break;
            case 10:
                x = 512;
                poly = 0x409;
                startWeight = 0x200;
                break;
            case 12:
                x = 2048;
                poly = 0x1069;
                startWeight = 0x800;
                break;
            default:
                throw new OkapiException("Unrecognized codeword size: " + codewordSize);
        }

        ReedSolomon rs = new ReedSolomon();
        int[] data = new int[dataBlocks + 3];
        int[] ecc = new int[eccBlocks + 3];

        for (int i = 0; i < dataBlocks; i++) {
            for (int weight = 0; weight < codewordSize; weight++) {
                if (adjustedString.charAt((i * codewordSize) + weight) == '1') {
                    data[i] += (x >> weight);
                }
            }
        }

        rs.init_gf(poly);
        rs.init_code(eccBlocks, 1);
        rs.encode(dataBlocks, data);

        for (int i = 0; i < eccBlocks; i++) {
            ecc[i] = rs.getResult(i);
        }

        for (int i = (eccBlocks - 1); i >= 0; i--) {
            for (int weight = startWeight; weight > 0; weight = weight >> 1) {
                if ((ecc[i] & weight) != 0) {
                    adjustedString.append('1');
                } else {
                    adjustedString.append('0');
                }
            }
        }
    }

    /** Determines codeword bit length - Table 3 */
    private static int getCodewordSize(int layers) {
        if (layers >= 23) {
            return 12;
        } else if (layers >= 9 && layers <= 22) {
            return 10;
        } else if (layers >= 3 && layers <= 8) {
            return 8;
        } else {
            assert layers <= 2;
            return 6;
        }
    }
}
 public class AztecRune extends Symbol {

    private static final int[] BIT_PLACEMENT_MAP = {
         1, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1,
         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        29, 1, 0, 0, 0, 0, 0, 0, 0, 1, 9,
        28, 1, 0, 1, 1, 1, 1, 1, 0, 1, 10,
        27, 1, 0, 1, 0, 0, 0, 1, 0, 1, 11,
        26, 1, 0, 1, 0, 1, 0, 1, 0, 1, 12,
        25, 1, 0, 1, 0, 0, 0, 1, 0, 1, 13,
        24, 1, 0, 1, 1, 1, 1, 1, 0, 1, 14,
        23, 1, 0, 0, 0, 0, 0, 0, 0, 1, 15,
         0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         0, 0, 22, 21, 20, 19, 18, 17, 16, 0, 0
    };

    @Override
    protected void encode() {

        if (!content.matches("[0-9]+")) {
            throw new OkapiException("Invalid input data");
        }

        int decimalValue = 0;
        switch (content.length()) {
            case 1:
                decimalValue = (content.charAt(0) - '0');
                break;
            case 2:
                decimalValue = 10 * (content.charAt(0) - '0');
                decimalValue += (content.charAt(1) - '0');
                break;
            case 3:
                decimalValue = 100 * (content.charAt(0) - '0');
                decimalValue += 10 * (content.charAt(1) - '0');
                decimalValue += (content.charAt(2) - '0');
                break;
            default:
                throw new OkapiException("Input too large");
        }

        if (decimalValue > 255) {
            throw new OkapiException("Input too large");
        }

        StringBuilder binaryDataStream = new StringBuilder(28);
        for (int i = 0x80; i > 0; i = i >> 1) {
            if ((decimalValue & i) != 0) {
                binaryDataStream.append('1');
            } else {
                binaryDataStream.append('0');
            }
        }

        int[] dataCodeword = new int[3];
        dataCodeword[0] = 0;
        dataCodeword[1] = 0;

        for (int i = 0; i < 2; i++) {
            if (binaryDataStream.charAt(i * 4) == '1') {
                dataCodeword[i] += 8;
            }
            if (binaryDataStream.charAt((i * 4) + 1) == '1') {
                dataCodeword[i] += 4;
            }
            if (binaryDataStream.charAt((i * 4) + 2) == '1') {
                dataCodeword[i] += 2;
            }
            if (binaryDataStream.charAt((i * 4) + 3) == '1') {
                dataCodeword[i] += 1;
            }
        }

        int[] errorCorrectionCodeword = new int[6];

        ReedSolomon rs = new ReedSolomon();
        rs.init_gf(0x13);
        rs.init_code(5, 1);
        rs.encode(2, dataCodeword);

        for (int i = 0; i < 5; i++) {
            errorCorrectionCodeword[i] = rs.getResult(i);
        }

        for (int i = 0; i < 5; i++) {
            if ((errorCorrectionCodeword[4 - i] & 0x08) != 0) {
                binaryDataStream.append('1');
            } else {
                binaryDataStream.append('0');
            }
            if ((errorCorrectionCodeword[4 - i] & 0x04) != 0) {
                binaryDataStream.append('1');
            } else {
                binaryDataStream.append('0');
            }
            if ((errorCorrectionCodeword[4 - i] & 0x02) != 0) {
                binaryDataStream.append('1');
            } else {
                binaryDataStream.append('0');
            }
            if ((errorCorrectionCodeword[4 - i] & 0x01) != 0) {
                binaryDataStream.append('1');
            } else {
                binaryDataStream.append('0');
            }
        }

        StringBuilder reversedBinaryDataStream = new StringBuilder(28);
        for (int i = 0; i < binaryDataStream.length(); i++) {
            if ((i & 1) == 0) {
                if (binaryDataStream.charAt(i) == '0') {
                    reversedBinaryDataStream.append('1');
                } else {
                    reversedBinaryDataStream.append('0');
                }
            } else {
                reversedBinaryDataStream.append(binaryDataStream.charAt(i));
            }
        }

        infoLine("Binary: " + reversedBinaryDataStream);

        readable = "";
        pattern = new String[11];
        row_count = 11;
        row_height = new int[11];

        for (int row = 0; row < 11; row++) {
            StringBuilder rowBinary = new StringBuilder(11);
            for (int column = 0; column < 11; column++) {
                if (BIT_PLACEMENT_MAP[(row * 11) + column] == 1) {
                    rowBinary.append('1');
                }
                if (BIT_PLACEMENT_MAP[(row * 11) + column] == 0) {
                    rowBinary.append('0');
                }
                if (BIT_PLACEMENT_MAP[(row * 11) + column] >= 2) {
                    rowBinary.append(reversedBinaryDataStream.charAt(BIT_PLACEMENT_MAP[(row * 11) + column] - 2));
                }
            }
            pattern[row] = bin2pat(rowBinary);
            row_height[row] = 1;
        }
    }
}
 public class ChannelCode extends Symbol {

    private int preferredNumberOfChannels;

    private int[] space = new int[11];
    private int[] bar = new int[11];
    private double currentValue;
    private double targetValue;

    /**
     * Sets the preferred number of channels used to encode data. This setting will be
     * ignored if the value to be encoded requires more channels.
     *
     * @param channels the preferred number of channels (3 to 8, inclusive)
     */
    public void setPreferredNumberOfChannels(int channels) {
        if (channels < 3 || channels > 8) {
            throw new IllegalArgumentException("Invalid Channel Code number of channels: " + channels);
        }
        preferredNumberOfChannels = channels;
    }

    /**
     * Returns the preferred number of channels used to encode data.
     *
     * @return the preferred number of channels used to encode data
     */
    public int getPreferredNumberOfChannels() {
        return preferredNumberOfChannels;
    }

    @Override
    protected void encode() {

        int channels;
        int i;
        int leadingZeroCount;

        if (content.length() > 7) {
            throw new OkapiException("Input too long");
        }

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

        if (preferredNumberOfChannels <= 2 || preferredNumberOfChannels > 8) {
            channels = 3;
        } else {
            channels = preferredNumberOfChannels;
        }

        targetValue = Integer.parseInt(content);

        switch (channels) {
            case 3:
                if (targetValue > 26) {
                    channels++;
                }
            case 4:
                if (targetValue > 292) {
                    channels++;
                }
            case 5:
                if (targetValue > 3493) {
                    channels++;
                }
            case 6:
                if (targetValue > 44072) {
                    channels++;
                }
            case 7:
                if (targetValue > 576688) {
                    channels++;
                }
            case 8:
                if (targetValue > 7742862) {
                    channels++;
                }
        }

        if (channels == 9) {
            throw new OkapiException("Value out of range");
        }

        infoLine("Channels Used: " + channels);

        for (i = 0; i < 11; i++) {
            bar[i] = 0;
            space[i] = 0;
        }

        bar[0] = space[1] = bar[1] = space[2] = bar[2] = 1;
        currentValue = 0;
        pattern = new String[1];
        nextSpace(channels, 3, channels, channels);

        leadingZeroCount = channels - 1 - content.length();

        readable = "";
        for (i = 0; i < leadingZeroCount; i++) {
            readable += "0";
        }
        readable += content;

        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void nextSpace(int channels, int i, int maxSpace, int maxBar) {
        for (int s = (i < channels + 2) ? 1 : maxSpace; s <= maxSpace; s++) {
            space[i] = s;
            nextBar(channels, i, maxBar, maxSpace + 1 - s);
        }
    }

    private void nextBar(int channels, int i, int maxBar, int maxSpace) {
        int b = (space[i] + bar[i - 1] + space[i - 1] + bar[i - 2] > 4) ? 1 : 2;
        if (i < channels + 2) {
            for (; b <= maxBar; b++) {
                bar[i] = b;
                nextSpace(channels, i + 1, maxSpace, maxBar + 1 - b);
            }
        } else if (b <= maxBar) {
            bar[i] = maxBar;
            checkIfDone();
            currentValue++;
        }
    }

    private void checkIfDone() {
        if (currentValue == targetValue) {
            /* Target reached - save the generated pattern */
            StringBuilder sb = new StringBuilder();
            sb.append("11110");
            for (int i = 0; i < 11; i++) {
                sb.append((char) (space[i] + '0'));
                sb.append((char) (bar[i] + '0'));
            }
            pattern[0] = sb.toString();
        }
    }
}
 public class Codabar extends Symbol {

    private static final String[] CODABAR_TABLE = {
        "11111221", "11112211", "11121121", "22111111", "11211211",
        "21111211", "12111121", "12112111", "12211111", "21121111",
        "11122111", "11221111", "21112121", "21211121", "21212111",
        "11212121", "11221211", "12121121", "11121221", "11122211"
    };

    private static final char[] CHARACTER_SET = {
        '0', '1', '2', '3', '4',
        '5', '6', '7', '8', '9',
        '-', '$', ':', '/', '.',
        '+', 'A', 'B', 'C', 'D'
    };

    /** Ratio of wide bar width to narrow bar width. */
    private double moduleWidthRatio = 2;

    /**
     * Sets the ratio of wide bar width to narrow bar width. Valid values are usually
     * between {@code 2} and {@code 3}. The default value is {@code 2}.
     *
     * @param moduleWidthRatio the ratio of wide bar width to narrow bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of wide bar width to narrow bar width.
     *
     * @return the ratio of wide bar width to narrow bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    /** {@inheritDoc} */
    @Override
    protected void encode() {

        if (!content.matches("[A-D]{1}[0-9:/\\$\\.\\+\u002D]+[A-D]{1}")) {
            throw new OkapiException("Invalid characters in input");
        }

        String horizontalSpacing = "";

        int l = content.length();
        for (int i = 0; i < l; i++) {
             horizontalSpacing += CODABAR_TABLE[positionOf(content.charAt(i), CHARACTER_SET)];
        }

        readable = content;
        pattern = new String[] { horizontalSpacing };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    /** {@inheritDoc} */
    @Override
    protected double getModuleWidth(int originalWidth) {
        if (originalWidth == 1) {
            return 1;
        } else {
            return moduleWidthRatio;
        }
    }

    /** {@inheritDoc} */
    @Override
    protected int[] getCodewords() {
        return getPatternAsCodewords(8);
    }
} public class CodablockF extends Symbol {

    private enum Mode {
        SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC, CANDB, CANDBB
    }

    private enum CfMode {
        MODEA, MODEB, MODEC
    }

    /* Annex A Table A.1 */
    private static final String[] C_128_TABLE = {
        "212222", "222122", "222221", "121223", "121322", "131222", "122213",
        "122312", "132212", "221213", "221312", "231212", "112232", "122132", "122231", "113222",
        "123122", "123221", "223211", "221132", "221231", "213212", "223112", "312131", "311222",
        "321122", "321221", "312212", "322112", "322211", "212123", "212321", "232121", "111323",
        "131123", "131321", "112313", "132113", "132311", "211313", "231113", "231311", "112133",
        "112331", "132131", "113123", "113321", "133121", "313121", "211331", "231131", "213113",
        "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111", "314111",
        "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214",
        "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112",
        "134111", "111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112",
        "421211", "212141", "214121", "412121", "111143", "111341", "131141", "114113", "114311",
        "411113", "411311", "113141", "114131", "311141", "411131", "211412", "211214", "211232",
        "2331112"};

    private int[][] blockmatrix = new int[44][62];
    private int columns_needed;
    private int rows_needed;
    private CfMode final_mode;
    private CfMode[] subset_selector = new CfMode[44];

    /**
     * TODO: It doesn't appear that this symbol should support GS1 (it's not in the GS1 spec and Zint doesn't
     * support GS1 with this type of symbology). However, the code below does contain GS1 checks, so we'll mark
     * it as supported for now. It's very possible that the code below which supports GS1 only does so because
     * it was originally copied from the Code 128 source code (just a suspicion, though).
     */
    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {

        int input_length, i, j, k;
        int min_module_height;
        Mode last_mode, this_mode;
        double estimate_codelength;
        String row_pattern;
        int[] row_indicator = new int[44];
        int[] row_check = new int[44];
        int k1_sum, k2_sum;
        int k1_check, k2_check;

        final_mode = CfMode.MODEA;

        if (!content.matches("[\u0000-\u00FF]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        inputData = toBytes(content, StandardCharsets.ISO_8859_1, 0x00);
        input_length = inputData.length - 1;

        if (input_length > 5450) {
            throw new OkapiException("Input data too long");
        }

        /* Make a guess at how many characters will be needed to encode the data */
        estimate_codelength = 0.0;
        last_mode = Mode.AORB; /* Codablock always starts with Code A */
        for (i = 0; i < input_length; i++) {
            this_mode = findSubset(inputData[i]);
            if (this_mode != last_mode) {
                estimate_codelength += 1.0;
            }
            if (this_mode != Mode.ABORC) {
                estimate_codelength += 1.0;
            } else {
                estimate_codelength += 0.5;
            }
            if (inputData[i] > 127) {
                estimate_codelength += 1.0;
            }
            last_mode = this_mode;
        }

        /* Decide symbol size based on the above guess */
        rows_needed = (int) (0.5 + Math.sqrt((estimate_codelength + 2) / 1.45));
        if (rows_needed < 2) {
            rows_needed = 2;
        }
        if (rows_needed > 44) {
            rows_needed = 44;
        }
        columns_needed = (int) (estimate_codelength + 2) / rows_needed;
        if (columns_needed < 4) {
            columns_needed = 4;
        }
        if (columns_needed > 62) {
            throw new OkapiException("Input data too long");
        }

        /* Encode the data */
        data_encode_blockf();

        /* Add check digits - Annex F */
        k1_sum = 0;
        k2_sum = 0;
        for(i = 0; i < input_length; i++) {
            if(inputData[i] == FNC1) {
                k1_sum += (i + 1) * 29; /* GS */
                k2_sum += i * 29;
            } else {
                k1_sum += (i + 1) * inputData[i];
                k2_sum += i * inputData[i];
            }
        }
        k1_check = k1_sum % 86;
        k2_check = k2_sum % 86;
        if((final_mode == CfMode.MODEA) || (final_mode == CfMode.MODEB)) {
            k1_check = k1_check + 64;
            if(k1_check > 95) { k1_check -= 96; }
            k2_check = k2_check + 64;
            if(k2_check > 95) { k2_check -= 96; }
        }
        blockmatrix[rows_needed - 1][columns_needed - 2] = k1_check;
        blockmatrix[rows_needed - 1][columns_needed - 1] = k2_check;

        /* Calculate row height (4.6.1.a) */
        min_module_height = (int) (0.55 * (columns_needed + 3)) + 3;
        if(min_module_height < 8) { min_module_height = 8; }

        /* Encode the Row Indicator in the First Row of the Symbol - Table D2 */
        if(subset_selector[0] == CfMode.MODEC) {
            /* Code C */
            row_indicator[0] = rows_needed - 2;
        } else {
            /* Code A or B */
            row_indicator[0] = rows_needed + 62;

            if(row_indicator[0] > 95) {
                row_indicator[0] -= 95;
            }
        }

        /* Encode the Row Indicator in the Second and Subsequent Rows of the Symbol - Table D3 */
        for(i = 1; i < rows_needed; i++) {
            /* Note that the second row is row number 1 because counting starts from 0 */
            if(subset_selector[i] == CfMode.MODEC) {
                /* Code C */
                row_indicator[i] = i + 42;
            } else {
                /* Code A or B */
                if( i < 6 )
                    row_indicator[i] = i + 10;
                else
                    row_indicator[i] = i + 20;
            }
        }

        /* Calculate row check digits - Annex E */
        for(i = 0; i < rows_needed; i++) {
            k = 103;
                    switch (subset_selector[i]) {
                        case MODEA: k += 98;
                            break;
                        case MODEB: k += 100;
                            break;
                        case MODEC: k += 99;
                            break;
                    }
            k += 2 * row_indicator[i];
            for(j = 0; j < columns_needed; j++) {
                k+= (j + 3) * blockmatrix[i][j];
            }
            row_check[i] = k % 103;
        }

        readable = "";
        row_count = rows_needed;
        pattern = new String[row_count];
        row_height = new int[row_count];

        infoLine("Grid Size: " + columns_needed + " X " + rows_needed);
        infoLine("K1 Check Digit: " + k1_check);
        infoLine("K2 Check Digit: " + k2_check);

        /* Resolve the data into patterns and place in symbol structure */
        info("Encoding: ");
        for(i = 0; i < rows_needed; i++) {

            row_pattern = "";
            /* Start character */
            row_pattern += C_128_TABLE[103]; /* Always Start A */

            switch (subset_selector[i]) {
                case MODEA:
                    row_pattern += C_128_TABLE[98];
                    info("MODEA ");
                    break;
                case MODEB:
                    row_pattern += C_128_TABLE[100];
                    info("MODEB ");
                    break;
                case MODEC:
                    row_pattern += C_128_TABLE[99];
                    info("MODEC ");
                    break;
            }
            row_pattern += C_128_TABLE[row_indicator[i]];
            infoSpace(row_indicator[i]);

            for (j = 0; j < columns_needed; j++) {
                row_pattern += C_128_TABLE[blockmatrix[i][j]];
                infoSpace(blockmatrix[i][j]);
            }

            row_pattern += C_128_TABLE[row_check[i]];
            info("(" + row_check[i] + ") ");

            /* Stop character */
            row_pattern += C_128_TABLE[106];

            /* Write the information into the symbol */
            pattern[i] = row_pattern;
            row_height[i] = 15;
        }
        infoLine();

        symbol_height = rows_needed * 15;
    }

    private Mode findSubset(int letter) {
        Mode mode;

        if (letter == FNC1) {
            mode = Mode.AORB;
        } else if (letter <= 31) {
            mode = Mode.SHIFTA;
        } else if ((letter >= 48) && (letter <= 57)) {
            mode = Mode.ABORC;
        } else if (letter <= 95) {
            mode = Mode.AORB;
        } else if (letter <= 127) {
            mode = Mode.SHIFTB;
        } else if (letter <= 159) {
            mode = Mode.SHIFTA;
        } else if (letter <= 223) {
            mode = Mode.AORB;
        } else {
            mode = Mode.SHIFTB;
        }

        return mode;
    }

    private void data_encode_blockf() {

        int i, j, input_position, current_row;
        int column_position, c;
        CfMode current_mode;
        boolean done, exit_status;
        int input_length = inputData.length - 1;

        exit_status = false;
        current_row = 0;
        current_mode = CfMode.MODEA;
        column_position = 0;
        input_position = 0;
        c = 0;

        do {
            done = false;
            /* 'done' ensures that the instructions are followed in the correct order for each input character */

            if (column_position == 0) {
                /* The Beginning of a row */
                c = columns_needed;
                current_mode = character_subset_select(input_position);
                subset_selector[current_row] = current_mode;
                if ((current_row == 0) && (inputDataType == DataType.GS1)) {
                    /* Section 4.4.7.1 */
                    blockmatrix[current_row][column_position] = 102; /* FNC1 */
                    column_position++;
                    c--;
                }
            }

            if (inputData[input_position] == FNC1) {
                blockmatrix[current_row][column_position] = 102; /* FNC1 */
                column_position++;
                c--;
                input_position++;
                done = true;
            }

            if (!done) {
                if (c <= 2) {
                    /* Annex B section 1 rule 1 */
                    /* Ensure that there is sufficient encodation capacity to continue (using the rules of Annex B.2). */
                    switch (current_mode) {
                        case MODEA: /* Table B1 applies */
                            if (findSubset(inputData[input_position]) == Mode.ABORC) {
                                blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                                column_position++;
                                c--;
                                input_position++;
                                done = true;
                            }

                            if ((findSubset(inputData[input_position]) == Mode.SHIFTB) && (c == 1)) {
                                /* Needs two symbols */
                                blockmatrix[current_row][column_position] = 100; /* Code B */
                                column_position++;
                                c--;
                                done = true;
                            }

                            if ((inputData[input_position] >= 244) && (!done)) {
                                /* Needs three symbols */
                                blockmatrix[current_row][column_position] = 100; /* Code B */
                                column_position++;
                                c--;
                                if (c == 1) {
                                    blockmatrix[current_row][column_position] = 101; /* Code A */
                                    column_position++;
                                    c--;
                                }
                                done = true;
                            }

                            if ((inputData[input_position] >= 128) && (!done) && c == 1) {
                                /* Needs two symbols */
                                blockmatrix[current_row][column_position] = 100; /* Code B */
                                column_position++;
                                c--;
                                done = true;
                            }
                            break;
                        case MODEB: /* Table B2 applies */
                            if (findSubset(inputData[input_position]) == Mode.ABORC) {
                                blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                                column_position++;
                                c--;
                                input_position++;
                                done = true;
                            }

                            if ((findSubset(inputData[input_position]) == Mode.SHIFTA) && (c == 1)) {
                                /* Needs two symbols */
                                blockmatrix[current_row][column_position] = 101; /* Code A */
                                column_position++;
                                c--;
                                done = true;
                            }

                            if (((inputData[input_position] >= 128)
                                    && (inputData[input_position] <= 159)) && (!done)) {
                                /* Needs three symbols */
                                blockmatrix[current_row][column_position] = 101; /* Code A */
                                column_position++;
                                c--;
                                if (c == 1) {
                                    blockmatrix[current_row][column_position] = 100; /* Code B */
                                    column_position++;
                                    c--;
                                }
                                done = true;
                            }

                            if ((inputData[input_position] >= 160) && (!done) && c == 1) {
                                /* Needs two symbols */
                                blockmatrix[current_row][column_position] = 101; /* Code A */
                                column_position++;
                                c--;
                                done = true;
                            }
                            break;
                        case MODEC: /* Table B3 applies */
                            if ((findSubset(inputData[input_position]) != Mode.ABORC) && (c == 1)) {
                                /* Needs two symbols */
                                blockmatrix[current_row][column_position] = 101; /* Code A */
                                column_position++;
                                c--;
                                done = true;
                            }

                            if (((findSubset(inputData[input_position]) == Mode.ABORC)
                                    && (findSubset(inputData[input_position + 1]) != Mode.ABORC))
                                    && (c == 1)) {
                                /* Needs two symbols */
                                blockmatrix[current_row][column_position] = 101; /* Code A */
                                column_position++;
                                c--;
                                done = true;
                            }

                            if (inputData[input_position] >= 128) {
                                /* Needs three symbols */
                                blockmatrix[current_row][column_position] = 101; /* Code A */
                                column_position++;
                                c--;
                                if (c == 1) {
                                    blockmatrix[current_row][column_position] = 100; /* Code B */
                                    column_position++;
                                    c--;
                                }
                            }
                            break;
                    }
                }
            }

            if (!done) {
                if (((findSubset(inputData[input_position]) == Mode.AORB)
                        || (findSubset(inputData[input_position]) == Mode.SHIFTA))
                        && (current_mode == CfMode.MODEA)) {
                    /* Annex B section 1 rule 2 */
                    /* If in Code Subset A and the next data character can be encoded in Subset A encode the next
                     character. */
                    if (inputData[input_position] >= 128) {
                        /* Extended ASCII character */
                        blockmatrix[current_row][column_position] = 101; /* FNC4 */
                        column_position++;
                        c--;
                    }
                    blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                    column_position++;
                    c--;
                    input_position++;
                    done = true;
                }
            }

            if (!done) {
                if (((findSubset(inputData[input_position]) == Mode.AORB)
                        || (findSubset(inputData[input_position]) == Mode.SHIFTB))
                        && (current_mode == CfMode.MODEB)) {
                    /* Annex B section 1 rule 3 */
                    /* If in Code Subset B and the next data character can be encoded in subset B, encode the next
                     character. */
                    if (inputData[input_position] >= 128) {
                        /* Extended ASCII character */
                        blockmatrix[current_row][column_position] = 100; /* FNC4 */
                        column_position++;
                        c--;
                    }
                    blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                    column_position++;
                    c--;
                    input_position++;
                    done = true;
                }
            }

            if (!done) {
                if (((findSubset(inputData[input_position]) == Mode.ABORC)
                        && (findSubset(inputData[input_position + 1]) == Mode.ABORC))
                        && (current_mode == CfMode.MODEC)) {
                    /* Annex B section 1 rule 4 */
                    /* If in Code Subset C and the next data are 2 digits, encode them. */
                    blockmatrix[current_row][column_position]
                            = ((inputData[input_position] - '0') * 10)
                            + (inputData[input_position + 1] - '0');
                    column_position++;
                    c--;
                    input_position += 2;
                    done = true;
                }
            }

            if (!done) {
                if (((current_mode == CfMode.MODEA) || (current_mode == CfMode.MODEB))
                        && ((findSubset(inputData[input_position]) == Mode.ABORC)
                        || (inputData[input_position] == FNC1))) {
                    // Count the number of numeric digits
                    // If 4 or more numeric data characters occur together when in subsets A or B:
                    //   a. If there is an even number of numeric data characters, insert a Code C character before the
                    //      first numeric digit to change to subset C.
                    //   b. If there is an odd number of numeric data characters, insert a Code Set C character immediately
                    //      after the first numeric digit to change to subset C.
                    i = 0;
                    j = 0;
                    do {
                        i++;
                        if (inputData[input_position + j] == FNC1) {
                            i++;
                        }
                        j++;
                    } while ((findSubset(inputData[input_position + j]) == Mode.ABORC)
                            || (inputData[input_position + j] == FNC1));
                    i--;

                    if (i >= 4) {
                        /* Annex B section 1 rule 5 */
                        if ((i % 2) == 1) {
                            /* Annex B section 1 rule 5a */
                            blockmatrix[current_row][column_position] = 99; /* Code C */
                            column_position++;
                            c--;
                            blockmatrix[current_row][column_position] = ((inputData[input_position] - '0') * 10)
                                    + (inputData[input_position + 1] - '0');
                            column_position++;
                            c--;
                            input_position += 2;
                            current_mode = CfMode.MODEC;
                        } else {
                            /* Annex B section 1 rule 5b */
                            blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                            column_position++;
                            c--;
                            input_position++;
                        }
                        done = true;
                    } else {
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                        done = true;
                    }
                }
            }

            if (!done) {
                if ((current_mode == CfMode.MODEB) && (findSubset(inputData[input_position]) == Mode.SHIFTA)) {
                    /* Annex B section 1 rule 6 */
                    /*  When in subset B and an ASCII control character occurs in the data:
                     a.   If there is a lower case character immediately following the control character, insert a Shift
                     character before the control character.
                     b.   Otherwise, insert a Code A character before the control character to change to subset A. */
                    if ((inputData[input_position + 1] >= 96) && (inputData[input_position + 1] <= 127)) {
                        /* Annex B section 1 rule 6a */
                        blockmatrix[current_row][column_position] = 98; /* Shift */
                        column_position++;
                        c--;
                        if (inputData[input_position] >= 128) {
                            /* Extended ASCII character */
                            blockmatrix[current_row][column_position] = 100; /* FNC4 */
                            column_position++;
                            c--;
                        }
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                    } else {
                        /* Annex B section 1 rule 6b */
                        blockmatrix[current_row][column_position] = 101; /* Code A */
                        column_position++;
                        c--;
                        if (inputData[input_position] >= 128) {
                            /* Extended ASCII character */
                            blockmatrix[current_row][column_position] = 100; /* FNC4 */
                            column_position++;
                            c--;
                        }
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                        current_mode = CfMode.MODEA;
                    }
                    done = true;
                }
            }

            if (!done) {
                if ((current_mode == CfMode.MODEA) && (findSubset(inputData[input_position]) == Mode.SHIFTB)) {
                    /* Annex B section 1 rule 7 */
                    /* When in subset A and a lower case character occurs in the data:
                     a.   If following that character, a control character occurs in the data before the occurrence of
                     another lower case character, insert a Shift character before the lower case character.
                     b.   Otherwise, insert a Code B character before the lower case character to change to subset B. */
                    if ((findSubset(inputData[input_position + 1]) == Mode.SHIFTA)
                            && (findSubset(inputData[input_position + 2]) == Mode.SHIFTB)) {
                        /* Annex B section 1 rule 7a */
                        blockmatrix[current_row][column_position] = 98; /* Shift */
                        column_position++;
                        c--;
                        if (inputData[input_position] >= 128) {
                            /* Extended ASCII character */
                            blockmatrix[current_row][column_position] = 101; /* FNC4 */
                            column_position++;
                            c--;
                        }
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                    } else {
                        /* Annex B section 1 rule 7b */
                        blockmatrix[current_row][column_position] = 100; /* Code B */
                        column_position++;
                        c--;
                        if (inputData[input_position] >= 128) {
                            /* Extended ASCII character */
                            blockmatrix[current_row][column_position] = 101; /* FNC4 */
                            column_position++;
                            c--;
                        }
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                        current_mode = CfMode.MODEB;
                    }
                    done = true;
                }
            }

            if (!done) {
                if ((current_mode == CfMode.MODEC) && ((findSubset(inputData[input_position]) != Mode.ABORC)
                        || (findSubset(inputData[input_position + 1]) != Mode.ABORC))) {
                    /* Annex B section 1 rule 8 */
                    /*  When in subset C and a non-numeric character (or a single digit) occurs in the data, insert a Code
                     A or Code B character before that character, following rules 8a and 8b to determine between code
                     subsets A and B.
                     a.    If an ASCII control character (eg NUL) occurs in the data before any lower case character, use
                     Code A.
                     b.    Otherwise use Code B. */
                    if (findSubset(inputData[input_position]) == Mode.SHIFTA) {
                        /* Annex B section 1 rule 8a */
                        blockmatrix[current_row][column_position] = 101; /* Code A */
                        column_position++;
                        c--;
                        if (inputData[input_position] >= 128) {
                            /* Extended ASCII character */
                            blockmatrix[current_row][column_position] = 101; /* FNC4 */
                            column_position++;
                            c--;
                        }
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                        current_mode = CfMode.MODEA;
                    } else {
                        /* Annex B section 1 rule 8b */
                        blockmatrix[current_row][column_position] = 100; /* Code B */
                        column_position++;
                        c--;
                        if (inputData[input_position] >= 128) {
                            /* Extended ASCII character */
                            blockmatrix[current_row][column_position] = 100; /* FNC4 */
                            column_position++;
                            c--;
                        }
                        blockmatrix[current_row][column_position] = a3_convert(inputData[input_position]);
                        column_position++;
                        c--;
                        input_position++;
                        current_mode = CfMode.MODEB;
                    }
                    done = true;
                }
            }

            if (input_position == input_length) {
                /* End of data - Annex B rule 5a */
                if (c == 1) {
                    if (current_mode == CfMode.MODEA) {
                        blockmatrix[current_row][column_position] = 100; /* Code B */
                        current_mode = CfMode.MODEB;
                    } else {
                        blockmatrix[current_row][column_position] = 101; /* Code A */
                        current_mode = CfMode.MODEA;
                    }
                    column_position++;
                    c--;
                }

                if (c == 0) {
                    /* Another row is needed */
                    column_position = 0;
                    c = columns_needed;
                    current_row++;
                    subset_selector[current_row] = CfMode.MODEA;
                    current_mode = CfMode.MODEA;
                }

                if (c > 2) {
                    /* Fill up the last row */
                    do {
                        if (current_mode == CfMode.MODEA) {
                            blockmatrix[current_row][column_position] = 100; /* Code B */
                            current_mode = CfMode.MODEB;
                        } else {
                            blockmatrix[current_row][column_position] = 101; /* Code A */
                            current_mode = CfMode.MODEA;
                        }
                        column_position++;
                        c--;
                    } while (c > 2);
                }

                /* If (c == 2) { do nothing } */

                exit_status = true;
                final_mode = current_mode;
            } else {
                if (c <= 0) {
                    /* Start new row - Annex B rule 5b */
                    column_position = 0;
                    current_row++;
                    if (current_row > 43) {
                        throw new OkapiException("Too many rows.");
                    }
                }
            }

        } while (!exit_status);

        if (current_row == 0) {
            /* fill up the first row */
            for(c = column_position; c <= columns_needed; c++) {
                if(current_mode == CfMode.MODEA) {
                    blockmatrix[current_row][c] = 100; /* Code B */
                    current_mode = CfMode.MODEB;
                } else {
                    blockmatrix[current_row][c] = 101; /* Code A */
                    current_mode = CfMode.MODEA;
                }
            }
            current_row++;
            /* add a second row */
            subset_selector[current_row] = CfMode.MODEA;
            current_mode = CfMode.MODEA;
            for(c = 0; c <= columns_needed - 2; c++) {
                if(current_mode == CfMode.MODEA) {
                    blockmatrix[current_row][c] = 100; /* Code B */
                    current_mode = CfMode.MODEB;
                } else {
                    blockmatrix[current_row][c] = 101; /* Code A */
                    current_mode = CfMode.MODEA;
                }
            }
        }

        rows_needed = current_row + 1;
    }

    private CfMode character_subset_select(int input_position) {

        /* Section 4.5.2 - Determining the Character Subset Selector in a Row */

        if((inputData[input_position] >= '0') && (inputData[input_position] <= '9')) {
            /* Rule 1 */
            return CfMode.MODEC;
        }

        if((inputData[input_position] >= 128) && (inputData[input_position] <= 160)) {
            /* Rule 2 (i) */
            return CfMode.MODEA;
        }

        if((inputData[input_position] >= 0) && (inputData[input_position] <= 31)) {
            /* Rule 3 */
            return CfMode.MODEA;
        }

        /* Rule 4 */
        return CfMode.MODEB;
    }

    private int a3_convert(int source) {
        /* Annex A section 3 */
        if(source < 32) { return source + 64; }
        if((source >= 32) && (source <= 127)) { return source - 32; }
        if((source >= 128) && (source <= 159)) { return (source - 128) + 64; }
        /* if source >= 160 */
        return (source - 128) - 32;
    }

    @Override
    protected void plotSymbol() {
        int xBlock, yBlock;
        int x, y, w, h;
        boolean black;

        rectangles.clear();
        y = 1;
        h = 1;
        for (yBlock = 0; yBlock < row_count; yBlock++) {
            black = true;
            x = 0;
            for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) {
                if (black) {
                    black = false;
                    w = pattern[yBlock].charAt(xBlock) - '0';
                    if (row_height[yBlock] == -1) {
                        h = default_height;
                    } else {
                        h = row_height[yBlock];
                    }
                    if (w != 0 && h != 0) {
                        Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
                        rectangles.add(rect);
                    }
                    if ((x + w) > symbol_width) {
                        symbol_width = x + w;
                    }
                } else {
                    black = true;
                }
                x += pattern[yBlock].charAt(xBlock) - '0';
            }
            y += h;
            if (y > symbol_height) {
                symbol_height = y;
            }
            /* Add bars between rows */
            if (yBlock != (row_count - 1)) {
                Rectangle2D.Double rect = new Rectangle2D.Double(11, y - 1, (symbol_width - 24), 2);
                rectangles.add(rect);
            }
        }

        /* Add top and bottom binding bars */
        Rectangle2D.Double top = new Rectangle2D.Double(0, 0, symbol_width, 2);
        rectangles.add(top);
        Rectangle2D.Double bottom = new Rectangle2D.Double(0, y - 1, symbol_width, 2);
        rectangles.add(bottom);
        symbol_height += 1;
    }
}
 public class Code11 extends Symbol {

    private static final String[] CODE_11_TABLE = {
        "111121", "211121", "121121", "221111", "112121", "212111",
        "122111", "111221", "211211", "211111", "112111"
    };

    private static final char[] CHARACTER_SET = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'
    };

    /** Ratio of wide bar width to narrow bar width. */
    private double moduleWidthRatio = 2;

    /** The number of check digits to calculate ({@code 1} or {@code 2}). */
    private int checkDigitCount = 2;

    /** Optional start delimiter to be shown in the human-readable text. */
    private Character startDelimiter;

    /** Optional stop delimiter to be shown in the human-readable text. */
    private Character stopDelimiter;

    /**
     * Sets the ratio of wide bar width to narrow bar width. Valid values are usually
     * between {@code 2} and {@code 3}. The default value is {@code 2}.
     *
     * @param moduleWidthRatio the ratio of wide bar width to narrow bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of wide bar width to narrow bar width.
     *
     * @return the ratio of wide bar width to narrow bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    /**
     * Sets the number of check digits to calculate ({@code 1} or {@code 2}). The default value is {@code 2}.
     *
     * @param checkDigitCount the number of check digits to calculate
     */
    public void setCheckDigitCount(int checkDigitCount) {
        if (checkDigitCount < 1 || checkDigitCount > 2) {
            throw new IllegalArgumentException("Check digit count must be 1 or 2.");
        }
        this.checkDigitCount = checkDigitCount;
    }

    /**
     * Returns the number of check digits to calculate (1 or 2).
     *
     * @return the number of check digits to calculate
     */
    public int getCheckDigitCount() {
        return checkDigitCount;
    }

    /**
     * Sets an optional start delimiter to be shown in the human-readable text (defaults to <code>null</code>).
     *
     * @param startDelimiter an optional start delimiter to be shown in the human-readable text
     */
    public void setStartDelimiter(Character startDelimiter) {
        this.startDelimiter = startDelimiter;
    }

    /**
     * Returns the optional start delimiter to be shown in the human-readable text.
     *
     * @return the optional start delimiter to be shown in the human-readable text
     */
    public Character getStartDelimiter() {
        return startDelimiter;
    }

    /**
     * Sets an optional stop delimiter to be shown in the human-readable text (defaults to <code>null</code>).
     *
     * @param stopDelimiter an optional stop delimiter to be shown in the human-readable text
     */
    public void setStopDelimiter(Character stopDelimiter) {
        this.stopDelimiter = stopDelimiter;
    }

    /**
     * Returns the optional stop delimiter to be shown in the human-readable text.
     *
     * @return the optional stop delimiter to be shown in the human-readable text
     */
    public Character getStopDelimiter() {
        return stopDelimiter;
    }

    /** {@inheritDoc} */
    @Override
    protected void encode() {

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

        String horizontalSpacing = "112211";
        String humanReadable = content;
        int length = content.length();
        int[] weight = new int[length + 1];

        for (int i = 0; i < length; i++) {
            char c = content.charAt(i);
            weight[i] = positionOf(c, CHARACTER_SET);
            horizontalSpacing += CODE_11_TABLE[weight[i]];
        }

        int checkDigitC = getCheckDigitC(weight, length);
        horizontalSpacing += CODE_11_TABLE[checkDigitC];
        humanReadable += CHARACTER_SET[checkDigitC];
        infoLine("Check Digit C: " + checkDigitC);

        if (checkDigitCount == 2) {
            weight[length] = checkDigitC;
            int checkDigitK = getCheckDigitK(weight, length + 1);
            horizontalSpacing += CODE_11_TABLE[checkDigitK];
            humanReadable += CHARACTER_SET[checkDigitK];
            infoLine("Check Digit K: " + checkDigitK);
        }

        horizontalSpacing += "112211";

        readable = humanReadable;
        if (startDelimiter != null) {
            readable = startDelimiter + readable;
        }
        if (stopDelimiter != null) {
            readable = readable + stopDelimiter;
        }

        pattern = new String[] { horizontalSpacing };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private static int getCheckDigitC(int[] weight, int length) {
        int countC = 0;
        int weightC = 1;
        for (int i = length - 1; i >= 0; i--) {
            countC += (weightC * weight[i]);
            weightC++;
            if (weightC > 10) {
                weightC = 1;
            }
        }
        return countC % 11;
    }

    private static int getCheckDigitK(int[] weight, int length) {
        int countK = 0;
        int weightK = 1;
        for (int i = length - 1; i >= 0; i--) {
            countK += (weightK * weight[i]);
            weightK++;
            if (weightK > 9) {
                weightK = 1;
            }
        }
        return countK % 11;
    }

    /** {@inheritDoc} */
    @Override
    protected double getModuleWidth(int originalWidth) {
        if (originalWidth == 1) {
            return 1;
        } else {
            return moduleWidthRatio;
        }
    }

    /** {@inheritDoc} */
    @Override
    protected int[] getCodewords() {
        return getPatternAsCodewords(6);
    }
}
 public class Code128 extends Symbol {

    private enum Mode {
        NULL, SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC
    }

    private enum FMode {
        SHIFTN, LATCHN, SHIFTF, LATCHF
    }

    private enum Composite {
        OFF, CCA, CCB, CCC
    }

    protected static final String[] CODE128_TABLE = {
        "212222", "222122", "222221", "121223", "121322", "131222", "122213",
        "122312", "132212", "221213", "221312", "231212", "112232", "122132",
        "122231", "113222", "123122", "123221", "223211", "221132", "221231",
        "213212", "223112", "312131", "311222", "321122", "321221", "312212",
        "322112", "322211", "212123", "212321", "232121", "111323", "131123",
        "131321", "112313", "132113", "132311", "211313", "231113", "231311",
        "112133", "112331", "132131", "113123", "113321", "133121", "313121",
        "211331", "231131", "213113", "213311", "213131", "311123", "311321",
        "331121", "312113", "312311", "332111", "314111", "221411", "431111",
        "111224", "111422", "121124", "121421", "141122", "141221", "112214",
        "112412", "122114", "122411", "142112", "142211", "241211", "221114",
        "413111", "241112", "134111", "111242", "121142", "121241", "114212",
        "124112", "124211", "411212", "421112", "421211", "212141", "214121",
        "412121", "111143", "111341", "131141", "114113", "114311", "411113",
        "411311", "113141", "114131", "311141", "411131", "211412", "211214",
        "211232", "2331112"
    };

    private boolean suppressModeC = false;
    private Composite compositeMode = Composite.OFF;

    /**
     * Optionally prevents this symbol from using subset mode C for numeric data compression.
     *
     * @param suppressModeC whether or not to prevent this symbol from using subset mode C
     */
    public void setSuppressModeC(boolean suppressModeC) {
        this.suppressModeC = suppressModeC;
    }

    /**
     * Returns whether or not this symbol is prevented from using subset mode C for numeric data compression.
     *
     * @return whether or not this symbol is prevented from using subset mode C for numeric data compression
     */
    public boolean getSuppressModeC() {
        return suppressModeC;
    }

    protected void setCca() {
        compositeMode = Composite.CCA;
    }

    protected void setCcb() {
        compositeMode = Composite.CCB;
    }

    protected void setCcc() {
        compositeMode = Composite.CCC;
    }

    public void unsetCc() {
        compositeMode = Composite.OFF;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {
        int i, j, k;
        int input_point = 0;
        Mode mode, last_mode;
        Mode last_set, current_set;
        double glyph_count;
        int bar_characters = 0, total_sum = 0;
        FMode f_state = FMode.LATCHN;
        Mode[] mode_type = new Mode[200];
        int[] mode_length = new int[200];
        int[] values = new int[200];
        int c;
        int linkage_flag = 0;
        int index_point = 0;
        int read = 0;

        inputData = toBytes(content, ISO_8859_1);
        if (inputData == null) {
            throw new OkapiException("Invalid characters in input data");
        }

        int sourcelen = inputData.length;

        FMode[] fset = new FMode[200];
        Mode[] set = new Mode[200]; /* set[] = Calculated mode for each character */

        if (sourcelen > 170) {
            throw new OkapiException("Input data too long");
        }

        /* Detect extended ASCII characters */
        for (i = 0; i < sourcelen; i++) {
            int ch = inputData[i];
            if (ch >= 128 && ch != FNC1 && ch != FNC2 && ch != FNC3 && ch != FNC4) {
                fset[i] = FMode.SHIFTF;
            } else {
                fset[i] = FMode.LATCHN;
            }
        }

        /* Decide when to latch to extended mode - Annex E note 3 */
        j = 0;
        for (i = 0; i < sourcelen; i++) {
            if (fset[i] == FMode.SHIFTF) {
                j++;
            } else {
                j = 0;
            }
            if (j >= 5) {
                for (k = i; k > (i - 5); k--) {
                    fset[k] = FMode.LATCHF;
                }
            }
            if ((j >= 3) && (i == (sourcelen - 1))) {
                for (k = i; k > (i - 3); k--) {
                    fset[k] = FMode.LATCHF;
                }
            }
        }

        /* Decide if it is worth reverting to 646 encodation for a few characters as described in 4.3.4.2 (d) */
        for (i = 1; i < sourcelen; i++) {
            if ((fset[i - 1] == FMode.LATCHF) && (fset[i] == FMode.LATCHN)) {
                /* Detected a change from 8859-1 to 646 - count how long for */
                for (j = 0; (fset[i + j] == FMode.LATCHN) && ((i + j) < sourcelen); j++);
                if ((j < 5) || ((j < 3) && ((i + j) == (sourcelen - 1)))) {
                    /* Uses the same figures recommended by Annex E note 3 */
                    /* Change to shifting back rather than latching back */
                    for (k = 0; k < j; k++) {
                        fset[i + k] = FMode.SHIFTN;
                    }
                }
            }
        }

        /* Decide on mode using same system as PDF417 and rules of ISO 15417 Annex E */
        int letter = inputData[input_point];
        int numbers = (letter >= '0' && letter <= '9' ? 1 : 0);
        mode = findSubset(letter, numbers);
        mode_type[0] = mode;
        mode_length[0] += length(letter, mode);
        for (i = 1; i < sourcelen; i++) {
            letter = inputData[i];
            last_mode = mode;
            mode = findSubset(letter, numbers);
            if (mode == last_mode) {
                mode_length[index_point] += length(letter, mode);
            } else {
                index_point++;
                mode_type[index_point] = mode;
                mode_length[index_point] = length(letter, mode);
            }
            if (letter >= '0' && letter <= '9') {
                numbers++;
            } else {
                numbers = 0;
            }
        }
        index_point++;
        index_point = reduceSubsetChanges(mode_type, mode_length, index_point);

        /* Put set data into set[] */
        read = 0;
        for (i = 0; i < index_point; i++) {
            for (j = 0; j < mode_length[i]; j++) {
                set[read] = mode_type[i];
                read++;
            }
        }

        /* Resolve odd length LATCHC blocks */
        int cs = 0, nums = 0, fncs = 0;
        for (i = 0; i < read; i++) {
            if (set[i] == Mode.LATCHC) {
                cs++;
                if (inputData[i] >= '0' && inputData[i] <= '9') {
                    nums++;
                } else if (inputData[i] == FNC1) {
                    fncs++;
                }
            } else {
                resolveOddCs(set, i, cs, nums, fncs);
                cs = 0;
                nums = 0;
                fncs = 0;
            }
        }
        resolveOddCs(set, i, cs, nums, fncs);

        /* Adjust for strings which start with shift characters - make them latch instead */
        if (set[0] == Mode.SHIFTA) {
            i = 0;
            do {
                set[i] = Mode.LATCHA;
                i++;
            } while (set[i] == Mode.SHIFTA);
        }
        if (set[0] == Mode.SHIFTB) {
            i = 0;
            do {
                set[i] = Mode.LATCHB;
                i++;
            } while (set[i] == Mode.SHIFTB);
        }

        /* Now we can calculate how long the barcode is going to be - and stop it from being too long */
        last_set = Mode.NULL;
        glyph_count = 0.0;
        for (i = 0; i < sourcelen; i++) {
            if ((set[i] == Mode.SHIFTA) || (set[i] == Mode.SHIFTB)) {
                glyph_count += 1.0;
            }
            if ((fset[i] == FMode.SHIFTF) || (fset[i] == FMode.SHIFTN)) {
                glyph_count += 1.0;
            }
            if (((set[i] == Mode.LATCHA) || (set[i] == Mode.LATCHB)) || (set[i] == Mode.LATCHC)) {
                if (set[i] != last_set) {
                    last_set = set[i];
                    glyph_count += 1.0;
                }
            }
            if (i == 0) {
                if (fset[i] == FMode.LATCHF) {
                    glyph_count += 2.0;
                }
            } else {
                if ((fset[i] == FMode.LATCHF) && (fset[i - 1] != FMode.LATCHF)) {
                    glyph_count += 2.0;
                }
                if ((fset[i] != FMode.LATCHF) && (fset[i - 1] == FMode.LATCHF)) {
                    glyph_count += 2.0;
                }
            }
            if (set[i] == Mode.LATCHC) {
                if (inputData[i] == FNC1) {
                    glyph_count += 1.0;
                } else {
                    glyph_count += 0.5;
                }
            } else {
                glyph_count += 1.0;
            }
        }
        if (glyph_count > 80.0) {
            throw new OkapiException("Input data too long");
        }

        info("Encoding: ");

        /* So now we know what start character to use - we can get on with it! */
        if (readerInit) {
            /* Reader Initialisation mode */
            switch (set[0]) {
                case LATCHA:
                    values[0] = 103;
                    current_set = Mode.LATCHA;
                    values[1] = 96;
                    bar_characters++;
                    info("STARTA FNC3 ");
                    break;
                case LATCHB:
                    values[0] = 104;
                    current_set = Mode.LATCHB;
                    values[1] = 96;
                    bar_characters++;
                    info("STARTB FNC3 ");
                    break;
                default: /* Start C */
                    values[0] = 104;
                    values[1] = 96;
                    values[2] = 99;
                    bar_characters += 2;
                    current_set = Mode.LATCHC;
                    info("STARTB FNC3 CODEC ");
                    break;
            }
        } else {
            /* Normal mode */
            switch (set[0]) {
                case LATCHA:
                    values[0] = 103;
                    current_set = Mode.LATCHA;
                    info("STARTA ");
                    break;
                case LATCHB:
                    values[0] = 104;
                    current_set = Mode.LATCHB;
                    info("STARTB ");
                    break;
                default:
                    values[0] = 105;
                    current_set = Mode.LATCHC;
                    info("STARTC ");
                    break;
            }
        }
        bar_characters++;

        if (inputDataType == DataType.GS1) {
            values[1] = 102;
            bar_characters++;
            info("FNC1 ");
        }

        if (fset[0] == FMode.LATCHF) {
            switch (current_set) {
                case LATCHA:
                    values[bar_characters] = 101;
                    values[bar_characters + 1] = 101;
                    info("FNC4 FNC4 ");
                    break;
                case LATCHB:
                    values[bar_characters] = 100;
                    values[bar_characters + 1] = 100;
                    info("FNC4 FNC4 ");
                    break;
            }
            bar_characters += 2;
            f_state = FMode.LATCHF;
        }

        /* Encode the data */
        read = 0;
        do {

            if ((read != 0) && (set[read] != current_set)) { /* Latch different code set */
                switch (set[read]) {
                    case LATCHA:
                        values[bar_characters] = 101;
                        bar_characters++;
                        current_set = Mode.LATCHA;
                        info("CODEA ");
                        break;
                    case LATCHB:
                        values[bar_characters] = 100;
                        bar_characters++;
                        current_set = Mode.LATCHB;
                        info("CODEB ");
                        break;
                    case LATCHC:
                        values[bar_characters] = 99;
                        bar_characters++;
                        current_set = Mode.LATCHC;
                        info("CODEC ");
                        break;
                }
            }

            if (read != 0) {
                if ((fset[read] == FMode.LATCHF) && (f_state == FMode.LATCHN)) {
                    /* Latch beginning of extended mode */
                    switch (current_set) {
                        case LATCHA:
                            values[bar_characters] = 101;
                            values[bar_characters + 1] = 101;
                            info("FNC4 FNC4 ");
                            break;
                        case LATCHB:
                            values[bar_characters] = 100;
                            values[bar_characters + 1] = 100;
                            info("FNC4 FNC4 ");
                            break;
                    }
                    bar_characters += 2;
                    f_state = FMode.LATCHF;
                }
                if ((fset[read] == FMode.LATCHN) && (f_state == FMode.LATCHF)) {
                    /* Latch end of extended mode */
                    switch (current_set) {
                        case LATCHA:
                            values[bar_characters] = 101;
                            values[bar_characters + 1] = 101;
                            info("FNC4 FNC4 ");
                            break;
                        case LATCHB:
                            values[bar_characters] = 100;
                            values[bar_characters + 1] = 100;
                            info("FNC4 FNC4 ");
                            break;
                    }
                    bar_characters += 2;
                    f_state = FMode.LATCHN;
                }
            }

            if ((fset[read] == FMode.SHIFTF) || (fset[read] == FMode.SHIFTN)) {
                /* Shift to or from extended mode */
                switch (current_set) {
                    case LATCHA:
                        values[bar_characters] = 101;
                        info("FNC4 ");
                        break;
                    case LATCHB:
                        values[bar_characters] = 100;
                        info("FNC4 ");
                        break;
                }
                bar_characters++;
            }

            if ((set[read] == Mode.SHIFTA) || (set[read] == Mode.SHIFTB)) {
                /* Insert shift character */
                values[bar_characters] = 98;
                info("SHFT ");
                bar_characters++;
            }

            /* Encode data characters */
            c = inputData[read];
            switch (set[read]) {
                case SHIFTA:
                case LATCHA:
                    if (c == FNC1) {
                        values[bar_characters] = 102;
                        info("FNC1 ");
                    } else if (c == FNC2) {
                        values[bar_characters] = 97;
                        info("FNC2 ");
                    } else if (c == FNC3) {
                        values[bar_characters] = 96;
                        info("FNC3 ");
                    } else if (c == FNC4) {
                        values[bar_characters] = 101;
                        info("FNC4 ");
                    } else if (c > 127) {
                        if (c < 160) {
                            values[bar_characters] = (c - 128) + 64;
                        } else {
                            values[bar_characters] = (c - 128) - 32;
                        }
                        infoSpace(values[bar_characters]);
                    } else {
                        if (c < 32) {
                            values[bar_characters] = c + 64;
                        } else {
                            values[bar_characters] = c - 32;
                        }
                        infoSpace(values[bar_characters]);
                    }
                    bar_characters++;
                    read++;
                    break;
                case SHIFTB:
                case LATCHB:
                    if (c == FNC1) {
                        values[bar_characters] = 102;
                        info("FNC1 ");
                    } else if (c == FNC2) {
                        values[bar_characters] = 97;
                        info("FNC2 ");
                    } else if (c == FNC3) {
                        values[bar_characters] = 96;
                        info("FNC3 ");
                    } else if (c == FNC4) {
                        values[bar_characters] = 100;
                        info("FNC4 ");
                    } else if (c > 127) {
                        values[bar_characters] = c - 32 - 128;
                        infoSpace(values[bar_characters]);
                    } else {
                        values[bar_characters] = c - 32;
                        infoSpace(values[bar_characters]);
                    }
                    bar_characters++;
                    read++;
                    break;
                case LATCHC:
                    if (c == FNC1) {
                        values[bar_characters] = 102;
                        info("FNC1 ");
                        bar_characters++;
                        read++;
                    } else {
                        int d = inputData[read + 1];
                        int weight = (10 * (c - '0')) + (d - '0');
                        values[bar_characters] = weight;
                        infoSpace(values[bar_characters]);
                        bar_characters++;
                        read += 2;
                    }
                    break;
            }

        } while (read < sourcelen);

        infoLine();

        /* "...note that the linkage flag is an extra code set character between
        the last data character and the Symbol Check Character" (GS1 Specification) */

        /* Linkage flags in GS1-128 are determined by ISO/IEC 24723 section 7.4 */

        switch (compositeMode) {
            case CCA:
            case CCB:
                /* CC-A or CC-B 2D component */
                switch(set[sourcelen - 1]) {
                    case LATCHA: linkage_flag = 100; break;
                    case LATCHB: linkage_flag = 99; break;
                    case LATCHC: linkage_flag = 101; break;
                }
                infoLine("Linkage Flag: " + linkage_flag);
                break;
            case CCC:
                /* CC-C 2D component */
                switch(set[sourcelen - 1]) {
                    case LATCHA: linkage_flag = 99; break;
                    case LATCHB: linkage_flag = 101; break;
                    case LATCHC: linkage_flag = 100; break;
                }
                infoLine("Linkage Flag: " + linkage_flag);
                break;
            default:
                break;
        }

        if (linkage_flag != 0) {
            values[bar_characters] = linkage_flag;
            bar_characters++;
        }

        infoLine("Data Codewords: " + bar_characters);

        /* Check digit calculation */
        for (i = 0; i < bar_characters; i++) {
            total_sum += (i == 0 ? values[i] : values[i] * i);
        }
        int checkDigit = total_sum % 103;
        infoLine("Check Digit: " + checkDigit);

        /* Build pattern string */
        StringBuilder dest = new StringBuilder((6 * bar_characters) + 6 + 7);
        for (i = 0; i < bar_characters; i++) {
            dest.append(CODE128_TABLE[values[i]]);
        }
        dest.append(CODE128_TABLE[checkDigit]);
        dest.append(CODE128_TABLE[106]); // stop character

        /* Readable text */
        if (inputDataType != DataType.GS1) {
            readable = removeFncEscapeSequences(content);
            if (inputDataType == DataType.HIBC) {
                readable = "*" + readable + "*";
            }
        }

        if (compositeMode == Composite.OFF) {
            pattern = new String[] { dest.toString() };
            row_height = new int[] { -1 };
            row_count = 1;
        } else {
            /* Add the separator pattern for composite symbols */
            pattern = new String[] { "0" + dest, dest.toString() };
            row_height = new int[] { 1, -1 };
            row_count = 2;
        }
    }

    private static String removeFncEscapeSequences(String s) {
        return s.replace(FNC1_STRING, "")
                .replace(FNC2_STRING, "")
                .replace(FNC3_STRING, "")
                .replace(FNC4_STRING, "");
    }

    private void resolveOddCs(Mode[] set, int i, int cs, int nums, int fncs) {
        if ((nums & 1) != 0) {
            int index;
            Mode m;
            if (i - cs == 0 || fncs > 0) {
                // Rule 2: first block -> swap last digit to A or B
                index = i - 1;
                if (index + 1 < set.length && set[index + 1] != null && set[index + 1] != Mode.LATCHC) {
                    // next block is either A or B -- match it
                    m = set[index + 1];
                } else {
                    // next block is C, or there is no next block -- just latch to B
                    m = Mode.LATCHB;
                }
            } else {
                // Rule 3b: subsequent block -> swap first digit to A or B
                // Note that we make an exception for C blocks which contain one (or more) FNC1 characters,
                // since swapping the first digit would place the FNC1 in an invalid position in the block
                index = i - nums;
                if (index - 1 >= 0 && set[index - 1] != null && set[index - 1] != Mode.LATCHC) {
                    // previous block is either A or B -- match it
                    m = set[index - 1];
                } else {
                    // previous block is C, or there is no previous block -- just latch to B
                    m = Mode.LATCHB;
                }
            }
            set[index] = m;
        }
    }

    private Mode findSubset(int letter, int numbers) {

        Mode mode;

        if (letter == FNC1) {
            if (numbers % 2 == 0) {
                /* ISO 15417 Annex E Note 2 */
                /* FNC1 may use subset C, so long as it doesn't break data into an odd number of digits */
                mode = Mode.ABORC;
            } else {
                mode = Mode.AORB;
            }
        } else if (letter == FNC2 || letter == FNC3 || letter == FNC4) {
            mode = Mode.AORB;
        } else if (letter <= 31) {
            mode = Mode.SHIFTA;
        } else if ((letter >= 48) && (letter <= 57)) {
            mode = Mode.ABORC;
        } else if (letter <= 95) {
            mode = Mode.AORB;
        } else if (letter <= 127) {
            mode = Mode.SHIFTB;
        } else if (letter <= 159) {
            mode = Mode.SHIFTA;
        } else if (letter <= 223) {
            mode = Mode.AORB;
        } else {
            mode = Mode.SHIFTB;
        }

        if (suppressModeC && mode == Mode.ABORC) {
            mode = Mode.AORB;
        }

        return mode;
    }

    private int length(int letter, Mode mode) {
        if (letter == FNC1 && mode == Mode.ABORC) {
            /* ISO 15417 Annex E Note 2 */
            /* Logical length used for making subset switching decisions, not actual length */
            return 2;
        } else {
            return 1;
        }
    }

    /** Implements rules from ISO 15417 Annex E. Returns the updated index point. */
    private int reduceSubsetChanges(Mode[] mode_type, int[] mode_length, int index_point) {

        int totalLength = 0;
        int i, length;
        Mode current, last, next;

        for (i = 0; i < index_point; i++) {
            current = mode_type[i];
            length = mode_length[i];
            if (i != 0) {
                last = mode_type[i - 1];
            } else {
                last = Mode.NULL;
            }
            if (i != index_point - 1) {
                next = mode_type[i + 1];
            } else {
                next = Mode.NULL;
            }

            /* ISO 15417 Annex E Note 2 */
            /* Calculate difference between logical length and actual length in this block */
            int extraLength = 0;
            for (int j = 0; j < length - extraLength; j++) {
                if (length(inputData[totalLength + j], current) == 2) {
                    extraLength++;
                }
            }

            if (i == 0) { /* first block */
                if ((index_point == 1) && ((length == 2) && (current == Mode.ABORC))) { /* Rule 1a */
                    mode_type[i] = Mode.LATCHC;
                    current = Mode.LATCHC;
                }
                if (current == Mode.ABORC) {
                    if (length >= 4) { /* Rule 1b */
                        mode_type[i] = Mode.LATCHC;
                        current = Mode.LATCHC;
                    } else {
                        mode_type[i] = Mode.AORB;
                        current = Mode.AORB;
                    }
                }
                if (current == Mode.SHIFTA) { /* Rule 1c */
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.AORB) && (next == Mode.SHIFTA)) { /* Rule 1c */
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if (current == Mode.AORB) { /* Rule 1d */
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
            } else {
                if ((current == Mode.ABORC) && (length >= 4)) { /* Rule 3 */
                    mode_type[i] = Mode.LATCHC;
                    current = Mode.LATCHC;
                }
                if (current == Mode.ABORC) {
                    mode_type[i] = Mode.AORB;
                    current = Mode.AORB;
                }
                if ((current == Mode.AORB) && (last == Mode.LATCHA)) {
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.AORB) && (last == Mode.LATCHB)) {
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.AORB) && (next == Mode.SHIFTA)) {
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.AORB) && (next == Mode.SHIFTB)) {
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if (current == Mode.AORB) {
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (length > 1)) { /* Rule 4 */
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (length > 1)) { /* Rule 5 */
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (last == Mode.LATCHA)) {
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (last == Mode.LATCHB)) {
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (next == Mode.AORB)) {
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (next == Mode.AORB)) {
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (last == Mode.LATCHC)) {
                    mode_type[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (last == Mode.LATCHC)) {
                    mode_type[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
            } /* Rule 2 is implemented elsewhere, Rule 6 is implied */

            /* ISO 15417 Annex E Note 2 */
            /* Convert logical length back to actual length for this block, now that we've decided on a subset */
            mode_length[i] -= extraLength;
            totalLength += mode_length[i];
        }

        return combineSubsetBlocks(mode_type, mode_length, index_point);
    }

    /** Modifies the specified mode and length arrays to combine adjacent modes of the same type, returning the updated index point. */
    private int combineSubsetBlocks(Mode[] mode_type, int[] mode_length, int index_point) {
        /* bring together same type blocks */
        if (index_point > 1) {
            for (int i = 1; i < index_point; i++) {
                if (mode_type[i - 1] == mode_type[i]) {
                    /* bring together */
                    mode_length[i - 1] = mode_length[i - 1] + mode_length[i];
                    /* decrease the list */
                    for (int j = i + 1; j < index_point; j++) {
                        mode_length[j - 1] = mode_length[j];
                        mode_type[j - 1] = mode_type[j];
                    }
                    index_point--;
                    i--;
                }
            }
        }
        return index_point;
    }

    /** {@inheritDoc} */
    @Override
    protected int[] getCodewords() {
        return getPatternAsCodewords(6);
    }
} public class Code16k extends Symbol {

    private enum Mode {
        NULL, SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC, CANDB, CANDBB
    }

    /* EN 12323 Table 1 - "Code 16K" character encodations */
    private static final String[] C16K_TABLE = {
        "212222", "222122", "222221", "121223", "121322", "131222", "122213",
        "122312", "132212", "221213", "221312", "231212", "112232", "122132",
        "122231", "113222", "123122", "123221", "223211", "221132", "221231",
        "213212", "223112", "312131", "311222", "321122", "321221", "312212",
        "322112", "322211", "212123", "212321", "232121", "111323", "131123",
        "131321", "112313", "132113", "132311", "211313", "231113", "231311",
        "112133", "112331", "132131", "113123", "113321", "133121", "313121",
        "211331", "231131", "213113", "213311", "213131", "311123", "311321",
        "331121", "312113", "312311", "332111", "314111", "221411", "431111",
        "111224", "111422", "121124", "121421", "141122", "141221", "112214",
        "112412", "122114", "122411", "142112", "142211", "241211", "221114",
        "413111", "241112", "134111", "111242", "121142", "121241", "114212",
        "124112", "124211", "411212", "421112", "421211", "212141", "214121",
        "412121", "111143", "111341", "131141", "114113", "114311", "411113",
        "411311", "113141", "114131", "311141", "411131", "211412", "211214",
        "211232", "211133"
    };

    /* EN 12323 Table 3 and Table 4 - Start patterns and stop patterns */
    private static final String[] C16K_START_STOP = {
        "3211", "2221", "2122", "1411", "1132", "1231", "1114", "3112"
    };

    /* EN 12323 Table 5 - Start and stop values defining row numbers */
    private static final int[] C16K_START_VALUES = {
        0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7
    };
    private static final int[] C16K_STOP_VALUES = {
        0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 0, 1, 2, 3
    };

    private Mode[] block_mode = new Mode[170]; /* RENAME block_mode */
    private int[] block_length = new int[170]; /* RENAME block_length */
    private int block_count;

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {

        // TODO: is it possible to share any of this code with Code128, which is more up to date?

        String width_pattern;
        int current_row, rows_needed, first_check, second_check;
        int indexchaine, pads_needed;
        char[] set, fset;
        Mode mode;
        char last_set, current_set;
        int i, j, k, m, read;
        int[] values;
        int bar_characters;
        double glyph_count;
        int first_sum, second_sum;
        int input_length;
        int c_count;
        boolean f_state;

        if (!content.matches("[\u0000-\u00FF]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        inputData = toBytes(content, StandardCharsets.ISO_8859_1);
        input_length = inputData.length;

        bar_characters = 0;
        set = new char[160];
        fset = new char[160];
        values = new int[160];

        if (input_length > 157) {
            throw new OkapiException("Input too long");
        }

        /* Detect extended ASCII characters */
        for (i = 0; i < input_length; i++) {
            if (inputData[i] >= 128) {
                fset[i] = 'f';
            } else {
                fset[i] = ' ';
            }
        }

        /* Decide when to latch to extended mode */
        for (i = 0; i < input_length; i++) {
            j = 0;
            if (fset[i] == 'f') {
                do {
                    j++;
                } while (fset[i + j] == 'f');
                if ((j >= 5) || ((j >= 3) && ((i + j) == (input_length - 1)))) {
                    for (k = 0; k <= j; k++) {
                        fset[i + k] = 'F';
                    }
                }
            }
        }

        /* Decide if it is worth reverting to 646 encodation for a few characters */
        if (input_length > 1) {
            for (i = 1; i < input_length; i++) {
                if ((fset[i - 1] == 'F') && (fset[i] == ' ')) {
                    /* Detected a change from 8859-1 to 646 - count how long for */
                    for (j = 0;
                    (fset[i + j] == ' ') && ((i + j) < input_length); j++);
                    if ((j < 5) || ((j < 3) && ((i + j) == (input_length - 1)))) {
                        /* Change to shifting back rather than latching back */
                        for (k = 0; k < j; k++) {
                            fset[i + k] = 'n';
                        }
                    }
                }
            }
        }

        /* Detect mode A, B and C characters */
        block_count = 0;
        indexchaine = 0;

        mode = findSubset(inputData[indexchaine]);
        if (inputData[indexchaine] == FNC1) {
            mode = Mode.ABORC;
        } /* FNC1 */

        for (i = 0; i < 160; i++) {
            block_length[i] = 0;
        }

        do {
            block_mode[block_count] = mode;
            while ((block_mode[block_count] == mode) && (indexchaine < input_length)) {
                block_length[block_count]++;
                indexchaine++;
                if (indexchaine < input_length) {
                    mode = findSubset(inputData[indexchaine]);
                    if (inputData[indexchaine] == FNC1) {
                        mode = Mode.ABORC;
                    } /* FNC1 */
                }
            }
            block_count++;
        } while (indexchaine < input_length);

        reduceSubsetChanges(block_count);


        /* Put set data into set[] */
        read = 0;
        for (i = 0; i < block_count; i++) {
            for (j = 0; j < block_length[i]; j++) {
                switch (block_mode[i]) {
                case SHIFTA:
                    set[read] = 'a';
                    break;
                case LATCHA:
                    set[read] = 'A';
                    break;
                case SHIFTB:
                    set[read] = 'b';
                    break;
                case LATCHB:
                    set[read] = 'B';
                    break;
                case LATCHC:
                    set[read] = 'C';
                    break;
                }
                read++;
            }
        }

        /* Adjust for strings which start with shift characters - make them latch instead */
        if (set[0] == 'a') {
            i = 0;
            do {
                set[i] = 'A';
                i++;
            } while (set[i] == 'a');
        }

        if (set[0] == 'b') {
            i = 0;
            do {
                set[i] = 'B';
                i++;
            } while (set[i] == 'b');
        }

        /* Watch out for odd-length Mode C blocks */
        c_count = 0;
        for (i = 0; i < read; i++) {
            if (set[i] == 'C') {
                if (inputData[i] == FNC1) {
                    if ((c_count & 1) != 0) {
                        if ((i - c_count) != 0) {
                            set[i - c_count] = 'B';
                        } else {
                            set[i - 1] = 'B';
                        }
                    }
                    c_count = 0;
                } else {
                    c_count++;
                }
            } else {
                if ((c_count & 1) != 0) {
                    if ((i - c_count) != 0) {
                        set[i - c_count] = 'B';
                    } else {
                        set[i - 1] = 'B';
                    }
                }
                c_count = 0;
            }
        }
        if ((c_count & 1) != 0) {
            if ((i - c_count) != 0) {
                set[i - c_count] = 'B';
            } else {
                set[i - 1] = 'B';
            }
        }
        for (i = 1; i < read - 1; i++) {
            if ((set[i] == 'C') && ((set[i - 1] == 'B') && (set[i + 1] == 'B'))) {
                set[i] = 'B';
            }
        }

        /* Make sure the data will fit in the symbol */
        last_set = ' ';
        glyph_count = 0.0;
        for (i = 0; i < input_length; i++) {
            if ((set[i] == 'a') || (set[i] == 'b')) {
                glyph_count = glyph_count + 1.0;
            }
            if ((fset[i] == 'f') || (fset[i] == 'n')) {
                glyph_count = glyph_count + 1.0;
            }
            if (((set[i] == 'A') || (set[i] == 'B')) || (set[i] == 'C')) {
                if (set[i] != last_set) {
                    last_set = set[i];
                    glyph_count = glyph_count + 1.0;
                }
            }
            if (i == 0) {
                if ((set[i] == 'B') && (set[1] == 'C')) {
                    glyph_count = glyph_count - 1.0;
                }
                if ((set[i] == 'B') && (set[1] == 'B') && set[2] == 'C') {
                    glyph_count = glyph_count - 1.0;
                }
                if (fset[i] == 'F') {
                    glyph_count = glyph_count + 2.0;
                }
            } else {
                if ((fset[i] == 'F') && (fset[i - 1] != 'F')) {
                    glyph_count = glyph_count + 2.0;
                }
                if ((fset[i] != 'F') && (fset[i - 1] == 'F')) {
                    glyph_count = glyph_count + 2.0;
                }
            }

            if ((set[i] == 'C') && (inputData[i] != FNC1)) {
                glyph_count = glyph_count + 0.5;
            } else {
                glyph_count = glyph_count + 1.0;
            }
        }

        if ((inputDataType == DataType.GS1) && (set[0] != 'A')) {
            /* FNC1 can be integrated with mode character */
            glyph_count--;
        }

        if (glyph_count > 77.0) {
            throw new OkapiException("Input too long");
        }

        /* Calculate how tall the symbol will be */
        glyph_count = glyph_count + 2.0;
        i = (int) glyph_count;
        rows_needed = (i / 5);
        if (i % 5 > 0) {
            rows_needed++;
        }

        if (rows_needed == 1) {
            rows_needed = 2;
        }

        /* start with the mode character - Table 2 */
        m = 0;
        switch (set[0]) {
        case 'A':
            m = 0;
            break;
        case 'B':
            m = 1;
            break;
        case 'C':
            m = 2;
            break;
        }

        if(readerInit) {
        	if(m == 2) { m = 5; }
        	if (inputDataType == DataType.GS1) {
        		throw new OkapiException("Cannot use both GS1 mode and Reader Initialisation");
        	} else {
        		if((set[0] == 'B') && (set[1] == 'C')) { m = 6; }
        	}
        	values[bar_characters] = (7 * (rows_needed - 2)) + m; /* see 4.3.4.2 */
        	values[bar_characters + 1] = 96; /* FNC3 */
        	bar_characters += 2;
        } else {
            if (inputDataType == DataType.GS1) {
                /* Integrate FNC1 */
                switch (set[0]) {
                case 'B':
                    m = 3;
                    break;
                case 'C':
                    m = 4;
                    break;
                }
            } else {
                if ((set[0] == 'B') && (set[1] == 'C')) {
                    m = 5;
                }
                if (((set[0] == 'B') && (set[1] == 'B')) && (set[2] == 'C')) {
                    m = 6;
                }
            }
        }
        values[bar_characters] = (7 * (rows_needed - 2)) + m; /* see 4.3.4.2 */
        bar_characters++;
        //}
        current_set = set[0];
        f_state = false;
        /* f_state remembers if we are in Extended ASCII mode (value 1) or
	in ISO/IEC 646 mode (value 0) */
        if (fset[0] == 'F') {
            switch (current_set) {
            case 'A':
                values[bar_characters] = 101;
                values[bar_characters + 1] = 101;
                break;
            case 'B':
                values[bar_characters] = 100;
                values[bar_characters + 1] = 100;
                break;
            }
            bar_characters += 2;
            f_state = true;
        }

        read = 0;

        /* Encode the data */
        do {

            if ((read != 0) && (set[read] != set[read - 1])) { /* Latch different code set */
                switch (set[read]) {
                case 'A':
                    values[bar_characters] = 101;
                    bar_characters++;
                    current_set = 'A';
                    break;
                case 'B':
                    values[bar_characters] = 100;
                    bar_characters++;
                    current_set = 'B';
                    break;
                case 'C':
                    if (!((read == 1) && (set[0] == 'B'))) { /* Not Mode C/Shift B */
                        if (!((read == 2) && ((set[0] == 'B') && (set[1] == 'B')))) {
                            /* Not Mode C/Double Shift B */
                            values[bar_characters] = 99;
                            bar_characters++;
                        }
                    }
                    current_set = 'C';
                    break;
                }
            }
            if (read != 0) {
                if ((fset[read] == 'F') && !f_state) {
                    /* Latch beginning of extended mode */
                    switch (current_set) {
                    case 'A':
                        values[bar_characters] = 101;
                        values[bar_characters + 1] = 101;
                        break;
                    case 'B':
                        values[bar_characters] = 100;
                        values[bar_characters + 1] = 100;
                        break;
                    }
                    bar_characters += 2;
                    f_state = true;
                }
                if ((fset[read] == ' ') && f_state) {
                    /* Latch end of extended mode */
                    switch (current_set) {
                    case 'A':
                        values[bar_characters] = 101;
                        values[bar_characters + 1] = 101;
                        break;
                    case 'B':
                        values[bar_characters] = 100;
                        values[bar_characters + 1] = 100;
                        break;
                    }
                    bar_characters += 2;
                    f_state = false;
                }
            }

            if ((fset[i] == 'f') || (fset[i] == 'n')) {
                /* Shift extended mode */
                switch (current_set) {
                case 'A':
                    values[bar_characters] = 101; /* FNC 4 */
                    break;
                case 'B':
                    values[bar_characters] = 100; /* FNC 4 */
                    break;
                }
                bar_characters++;
            }

            if ((set[i] == 'a') || (set[i] == 'b')) {
                /* Insert shift character */
                values[bar_characters] = 98;
                bar_characters++;
            }

            if (inputData[read] != FNC1) {
                switch (set[read]) { /* Encode data characters */
                case 'A':
                case 'a':
                    getValueSubsetA(inputData[read], values, bar_characters);
                    bar_characters++;
                    read++;
                    break;
                case 'B':
                case 'b':
                    getValueSubsetB(inputData[read], values, bar_characters);
                    bar_characters++;
                    read++;
                    break;
                case 'C':
                    getValueSubsetC(inputData[read], inputData[read + 1], values, bar_characters);
                    bar_characters++;
                    read += 2;
                    break;
                }
            } else {
                values[bar_characters] = 102;
                bar_characters++;
                read++;
            }

        } while (read < input_length);

        pads_needed = 5 - ((bar_characters + 2) % 5);
        if (pads_needed == 5) {
            pads_needed = 0;
        }
        if ((bar_characters + pads_needed) < 8) {
            pads_needed += 8 - (bar_characters + pads_needed);
        }
        for (i = 0; i < pads_needed; i++) {
            values[bar_characters] = 106;
            bar_characters++;
        }

        /* Calculate check digits */
        first_sum = 0;
        second_sum = 0;
        for (i = 0; i < bar_characters; i++) {
            first_sum += (i + 2) * values[i];
            second_sum += (i + 1) * values[i];
        }
        first_check = first_sum % 107;
        second_sum += first_check * (bar_characters + 1);
        second_check = second_sum % 107;
        values[bar_characters] = first_check;
        values[bar_characters + 1] = second_check;
        bar_characters += 2;

        readable = "";
        pattern = new String[rows_needed];
        row_count = rows_needed;
        row_height = new int[rows_needed];

        infoLine("Symbol Rows: " + rows_needed);
        infoLine("First Check Digit: " + first_check);
        infoLine("Second Check Digit: " + second_check);
        info("Codewords: ");

        for (current_row = 0; current_row < rows_needed; current_row++) {

            width_pattern = "";
            width_pattern += C16K_START_STOP[C16K_START_VALUES[current_row]];
            width_pattern += "1";
            for (i = 0; i < 5; i++) {
                width_pattern += C16K_TABLE[values[(current_row * 5) + i]];
                infoSpace(values[(current_row * 5) + i]);
            }
            width_pattern += C16K_START_STOP[C16K_STOP_VALUES[current_row]];

            pattern[current_row] = width_pattern;
            row_height[current_row] = 10;
        }
        infoLine();
    }

    private void getValueSubsetA(int source, int[] values, int bar_chars) {
        if (source > 127) {
            if (source < 160) {
                values[bar_chars] = source + 64 - 128;
            } else {
                values[bar_chars] = source - 32 - 128;
            }
        } else {
            if (source < 32) {
                values[bar_chars] = source + 64;
            } else {
                values[bar_chars] = source - 32;
            }
        }
    }

    private void getValueSubsetB(int source, int[] values, int bar_chars) {
        if (source > 127) {
            values[bar_chars] = source - 32 - 128;
        } else {
            values[bar_chars] = source - 32;
        }
    }

    private void getValueSubsetC(int source_a, int source_b, int[] values, int bar_chars) {
        int weight;

        weight = (10 * Character.getNumericValue(source_a)) + Character.getNumericValue(source_b);
        values[bar_chars] = weight;
    }

    private Mode findSubset(int letter) {
        Mode mode;

        if (letter <= 31) {
            mode = Mode.SHIFTA;
        } else if ((letter >= 48) && (letter <= 57)) {
            mode = Mode.ABORC;
        } else if (letter <= 95) {
            mode = Mode.AORB;
        } else if (letter <= 127) {
            mode = Mode.SHIFTB;
        } else if (letter <= 159) {
            mode = Mode.SHIFTA;
        } else if (letter <= 223) {
            mode = Mode.AORB;
        } else {
            mode = Mode.SHIFTB;
        }

        return mode;
    }

    private void reduceSubsetChanges(int block_count) { /* Implements rules from ISO 15417 Annex E */
        int i, length;
        Mode current, last, next;

        for (i = 0; i < block_count; i++) {
            current = block_mode[i];
            length = block_length[i];
            if (i != 0) {
                last = block_mode[i - 1];
            } else {
                last = Mode.NULL;
            }
            if (i != block_count - 1) {
                next = block_mode[i + 1];
            } else {
                next = Mode.NULL;
            }

            if (i == 0) { /* first block */
                if ((block_count == 1) && ((length == 2) && (current == Mode.ABORC))) { /* Rule 1a */
                    block_mode[i] = Mode.LATCHC;
                }
                if (current == Mode.ABORC) {
                    if (length >= 4) { /* Rule 1b */
                        block_mode[i] = Mode.LATCHC;
                    } else {
                        block_mode[i] = Mode.AORB;
                        current = Mode.AORB;
                    }
                }
                if (current == Mode.SHIFTA) { /* Rule 1c */
                    block_mode[i] = Mode.LATCHA;
                }
                if ((current == Mode.AORB) && (next == Mode.SHIFTA)) { /* Rule 1c */
                    block_mode[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if (current == Mode.AORB) { /* Rule 1d */
                    block_mode[i] = Mode.LATCHB;
                }
            } else {
                if ((current == Mode.ABORC) && (length >= 4)) { /* Rule 3 */
                    block_mode[i] = Mode.LATCHC;
                    current = Mode.LATCHC;
                }
                if (current == Mode.ABORC) {
                    block_mode[i] = Mode.AORB;
                    current = Mode.AORB;
                }
                if ((current == Mode.AORB) && (last == Mode.LATCHA)) {
                    block_mode[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.AORB) && (last == Mode.LATCHB)) {
                    block_mode[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.AORB) && (next == Mode.SHIFTA)) {
                    block_mode[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.AORB) && (next == Mode.SHIFTB)) {
                    block_mode[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if (current == Mode.AORB) {
                    block_mode[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (length > 1)) { /* Rule 4 */
                    block_mode[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (length > 1)) { /* Rule 5 */
                    block_mode[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (last == Mode.LATCHA)) {
                    block_mode[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (last == Mode.LATCHB)) {
                    block_mode[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
                if ((current == Mode.SHIFTA) && (last == Mode.LATCHC)) {
                    block_mode[i] = Mode.LATCHA;
                    current = Mode.LATCHA;
                }
                if ((current == Mode.SHIFTB) && (last == Mode.LATCHC)) {
                    block_mode[i] = Mode.LATCHB;
                    current = Mode.LATCHB;
                }
            } /* Rule 2 is implimented elsewhere, Rule 6 is implied */
        }
        combineSubsetBlocks(block_count);

    }

    private void combineSubsetBlocks(int block_count) {
        int i, j;

        /* bring together same type blocks */
        if (block_count > 1) {
            i = 1;
            while (i < block_count) {
                if (block_mode[i - 1] == block_mode[i]) {
                    /* bring together */
                    block_length[i - 1] = block_length[i - 1] + block_length[i];
                    j = i + 1;

                    /* decreace the list */
                    while (j < block_count) {
                        block_length[j - 1] = block_length[j];
                        block_mode[j - 1] = block_mode[j];
                        j++;
                    }
                    block_count = block_count - 1;
                    i--;
                }
                i++;
            }
        }
    }

    @Override
    protected void plotSymbol() {
        int xBlock, yBlock;
        int x, y, w, h;
        boolean black;

        rectangles.clear();
        y = 1;
        h = 1;
        for (yBlock = 0; yBlock < row_count; yBlock++) {
            black = true;
            x = 15;
            for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) {
                if (black) {
                    black = false;
                    w = pattern[yBlock].charAt(xBlock) - '0';
                    if (row_height[yBlock] == -1) {
                        h = default_height;
                    } else {
                        h = row_height[yBlock];
                    }
                    if (w != 0 && h != 0) {
                        Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
                        rectangles.add(rect);
                    }
                    if ((x + w) > symbol_width) {
                        symbol_width = x + w;
                    }
                } else {
                    black = true;
                }
                x += pattern[yBlock].charAt(xBlock) - '0';
            }
            y += h;
            if (y > symbol_height) {
                symbol_height = y;
            }
            /* Add bars between rows */
            if (yBlock != (row_count - 1)) {
                Rectangle2D.Double rect = new Rectangle2D.Double(15, y - 1, (symbol_width - 15), 2);
                rectangles.add(rect);
            }
        }

        /* Add top and bottom binding bars */
        Rectangle2D.Double top = new Rectangle2D.Double(0, 0, (symbol_width + 15), 2);
        rectangles.add(top);
        Rectangle2D.Double bottom = new Rectangle2D.Double(0, y - 1, (symbol_width + 15), 2);
        rectangles.add(bottom);
        symbol_width += 15;
        symbol_height += 1;
    }
}
 public class Code2Of5 extends Symbol {

    public enum ToFMode {
        /**
         * Standard Code 2 of 5 mode, also known as Code 2 of 5 Matrix. Encodes any
         * length numeric input (digits 0-9). This is the default mode.
         */
        MATRIX,
        /**
         * Industrial Code 2 of 5 which can encode any length numeric input (digits 0-9)
         * and does not include a check digit.
         */
        INDUSTRIAL,
        /**
         * International Air Transport Agency variation of Code 2 of 5. Encodes any length
         * numeric input (digits 0-9) and does not include a check digit.
         */
        IATA,
        /**
         * Code 2 of 5 Data Logic. Encodes any length numeric input (digits 0-9) and does
         * not include a check digit.
         */
        DATA_LOGIC,
        /**
         * Interleaved Code 2 of 5. Encodes pairs of numbers, and so can only encode an even
         * number of digits (0-9). If an odd number of digits is entered a leading zero is
         * added. No check digit is calculated.
         */
        INTERLEAVED,
        /**
         * Interleaved Code 2 of 5 with check digit. Encodes pairs of numbers, and so can
         * only encode an even number of digits (0-9). If adding the check digit results
         * in an odd number of digits then a leading zero is added.
         */
        INTERLEAVED_WITH_CHECK_DIGIT,
        /**
         * ITF-14, also known as UPC Shipping Container Symbol or Case Code. Requires a
         * 13-digit numeric input (digits 0-9). One modulo-10 check digit is calculated.
         */
        ITF14,
        /**
         * Deutsche Post Leitcode. Requires a 13-digit numerical input. Check digit is
         * calculated.
         */
        DP_LEITCODE,
        /**
         * Deutsche Post Identcode. Requires an 11-digit numerical input. Check digit is
         * calculated.
         */
        DP_IDENTCODE
    }

    private static final String[] C25_MATRIX_TABLE = {
        "113311", "311131", "131131", "331111", "113131", "313111", "133111", "111331", "311311", "131311"
    };

    private static final String[] C25_INDUSTRIAL_TABLE = {
        "1111313111", "3111111131", "1131111131", "3131111111", "1111311131", "3111311111", "1131311111", "1111113131", "3111113111", "1131113111"
    };

    private static final String[] C25_INTERLEAVED_TABLE = {
        "11331", "31113", "13113", "33111", "11313", "31311", "13311", "11133", "31131", "13131"
    };

    /** The 2-of-5 mode. */
    private ToFMode mode = ToFMode.MATRIX;

    /** Ratio of wide bar width to narrow bar width. */
    private double moduleWidthRatio = 3;

    /**
     * Sets the 2-of-5 mode. The default value is {@link ToFMode#MATRIX}.
     *
     * @param mode the 2-of-5 mode
     */
    public void setMode(ToFMode mode) {
        this.mode = mode;
    }

    /**
     * Returns the 2-of-5 mode.
     *
     * @return the 2-of-5 mode
     */
    public ToFMode getMode() {
        return mode;
    }

    /**
     * Sets the ratio of wide bar width to narrow bar width. Valid values are usually
     * between {@code 2} and {@code 3}. The default value is {@code 3}.
     *
     * @param moduleWidthRatio the ratio of wide bar width to narrow bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of wide bar width to narrow bar width.
     *
     * @return the ratio of wide bar width to narrow bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    @Override
    protected void encode() {
        switch (mode) {
            case MATRIX:
                dataMatrix();
                break;
            case INDUSTRIAL:
                industrial();
                break;
            case IATA:
                iata();
                break;
            case INTERLEAVED:
                interleaved(false);
                break;
            case INTERLEAVED_WITH_CHECK_DIGIT:
                interleaved(true);
                break;
            case DATA_LOGIC:
                dataLogic();
                break;
            case ITF14:
                itf14();
                break;
            case DP_LEITCODE:
                deutschePostLeitcode();
                break;
            case DP_IDENTCODE:
                deutschePostIdentcode();
                break;
        }
    }

    private void dataMatrix() {

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        String dest = "311111";
        for (int i = 0; i < content.length(); i++) {
            dest += C25_MATRIX_TABLE[Character.getNumericValue(content.charAt(i))];
        }
        dest += "31111";

        readable = content;
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void industrial() {

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        String dest = "313111";
        for (int i = 0; i < content.length(); i++) {
            dest += C25_INDUSTRIAL_TABLE[Character.getNumericValue(content.charAt(i))];
        }
        dest += "31113";

        readable = content;
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void iata() {

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        String dest = "1111";
        for (int i = 0; i < content.length(); i++) {
            dest += C25_INDUSTRIAL_TABLE[Character.getNumericValue(content.charAt(i))];
        }
        dest += "311";

        readable = content;
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void dataLogic() {

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        String dest = "1111";
        for (int i = 0; i < content.length(); i++) {
            dest += C25_MATRIX_TABLE[Character.getNumericValue(content.charAt(i))];
        }
        dest += "311";

        readable = content;
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void interleaved(boolean addCheckDigit) {
        int i;
        String dest;

        readable = content;

        if (!readable.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        if (addCheckDigit) {
            char checkDigit = checkDigit(readable, 1, 3);
            readable += checkDigit;
            infoLine("Check Digit: " + checkDigit);
        }

        if ((readable.length() & 1) != 0) {
            readable = "0" + readable;
        }

        dest = "1111";
        for (i = 0; i < readable.length(); i += 2) {
            dest += interlace(i, i + 1);
        }
        dest += "311";

        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private String interlace(int x, int y) {
        char a = readable.charAt(x);
        char b = readable.charAt(y);

        String one = C25_INTERLEAVED_TABLE[Character.getNumericValue(a)];
        String two = C25_INTERLEAVED_TABLE[Character.getNumericValue(b)];

        StringBuilder f = new StringBuilder(10);
        for (int i = 0; i < 5; i++) {
            f.append(one.charAt(i));
            f.append(two.charAt(i));
        }

        return f.toString();
    }

    private void itf14() {
        int i;
        int input_length = content.length();
        String dest;

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        if (input_length > 13) {
            throw new OkapiException("Input data too long");
        }

        readable = "";
        for (i = input_length; i < 13; i++) {
            readable += "0";
        }
        readable += content;

        char checkDigit = checkDigit(readable, 1, 3);
        readable += checkDigit;
        infoLine("Check Digit: " + checkDigit);

        dest = "1111";
        for (i = 0; i < readable.length(); i += 2) {
            dest += interlace(i, i + 1);
        }
        dest += "311";

        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void deutschePostLeitcode() {
        int i;
        int input_length = content.length();
        String dest;

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        if (input_length > 13) {
            throw new OkapiException("Input data too long");
        }

        readable = "";
        for (i = input_length; i < 13; i++) {
            readable += "0";
        }
        readable += content;

        char checkDigit = checkDigit(readable, 9, 4);
        readable += checkDigit;
        infoLine("Check digit: " + checkDigit);

        dest = "1111";
        for (i = 0; i < readable.length(); i += 2) {
            dest += interlace(i, i + 1);
        }
        dest += "311";

        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void deutschePostIdentcode() {
        int i;
        int input_length = content.length();
        String dest;

        if (!content.matches("[0-9]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        if (input_length > 11) {
            throw new OkapiException("Input data too long");
        }

        readable = "";
        for (i = input_length; i < 11; i++) {
            readable += "0";
        }
        readable += content;

        char checkDigit = checkDigit(readable, 9, 4);
        readable += checkDigit;
        infoLine("Check Digit: " + checkDigit);

        dest = "1111";
        for (i = 0; i < readable.length(); i += 2) {
            dest += interlace(i, i + 1);
        }
        dest += "311";

        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private static char checkDigit(String s, int multiplier1, int multiplier2) {
        int count = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            if ((i & 1) != 0) {
                count += multiplier1 * (s.charAt(i) - '0');
            } else {
                count += multiplier2 * (s.charAt(i) - '0');
            }
        }
        return (char) (((10 - (count % 10)) % 10) + '0');
    }

    @Override
    protected void plotSymbol() {

        int xBlock;

        rectangles.clear();
        texts.clear();

        int baseY;
        if (humanReadableLocation == TOP) {
            baseY = getTheoreticalHumanReadableHeight();
        } else {
            baseY = 0;
        }

        double x = 0;
        int y = baseY;
        int h = 0;
        boolean black = true;

        int offset = 0;
        if (mode == ToFMode.ITF14) {
            offset = 20;
        }

        for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {
            char c = pattern[0].charAt(xBlock);
            double w = getModuleWidth(c - '0') * moduleWidth;
            if (black) {
                if (row_height[0] == -1) {
                    h = default_height;
                } else {
                    h = row_height[0];
                }
                if (w != 0 && h != 0) {
                    Rectangle2D.Double rect = new Rectangle2D.Double(x + offset, y, w, h);
                    rectangles.add(rect);
                }
                symbol_width = (int) Math.ceil(x + w + (2 * offset));
            }
            black = !black;
            x += w;
        }

        symbol_height = h;

        if (mode == ToFMode.ITF14) {
            // Add bounding box
            Rectangle2D.Double topBar = new Rectangle2D.Double(0, baseY, symbol_width, 4);
            Rectangle2D.Double bottomBar = new Rectangle2D.Double(0, baseY + symbol_height - 4, symbol_width, 4);
            Rectangle2D.Double leftBar = new Rectangle2D.Double(0, baseY, 4, symbol_height);
            Rectangle2D.Double rightBar = new Rectangle2D.Double(symbol_width - 4, baseY, 4, symbol_height);
            rectangles.add(topBar);
            rectangles.add(bottomBar);
            rectangles.add(leftBar);
            rectangles.add(rightBar);
        }

        if (humanReadableLocation != NONE && !readable.isEmpty()) {
            double baseline;
            if (humanReadableLocation == TOP) {
                baseline = fontSize;
            } else {
                baseline = symbol_height + fontSize;
            }
            texts.add(new TextBox(0, baseline, symbol_width, readable, humanReadableAlignment));
        }
    }

    /** {@inheritDoc} */
    @Override
    protected double getModuleWidth(int originalWidth) {
        if (originalWidth == 1) {
            return 1;
        } else {
            return moduleWidthRatio;
        }
    }
} public enum ToFMode {
    /**
     * Standard Code 2 of 5 mode, also known as Code 2 of 5 Matrix. Encodes any
     * length numeric input (digits 0-9). This is the default mode.
     */
    MATRIX,
    /**
     * Industrial Code 2 of 5 which can encode any length numeric input (digits 0-9)
     * and does not include a check digit.
     */
    INDUSTRIAL,
    /**
     * International Air Transport Agency variation of Code 2 of 5. Encodes any length
     * numeric input (digits 0-9) and does not include a check digit.
     */
    IATA,
    /**
     * Code 2 of 5 Data Logic. Encodes any length numeric input (digits 0-9) and does
     * not include a check digit.
     */
    DATA_LOGIC,
    /**
     * Interleaved Code 2 of 5. Encodes pairs of numbers, and so can only encode an even
     * number of digits (0-9). If an odd number of digits is entered a leading zero is
     * added. No check digit is calculated.
     */
    INTERLEAVED,
    /**
     * Interleaved Code 2 of 5 with check digit. Encodes pairs of numbers, and so can
     * only encode an even number of digits (0-9). If adding the check digit results
     * in an odd number of digits then a leading zero is added.
     */
    INTERLEAVED_WITH_CHECK_DIGIT,
    /**
     * ITF-14, also known as UPC Shipping Container Symbol or Case Code. Requires a
     * 13-digit numeric input (digits 0-9). One modulo-10 check digit is calculated.
     */
    ITF14,
    /**
     * Deutsche Post Leitcode. Requires a 13-digit numerical input. Check digit is
     * calculated.
     */
    DP_LEITCODE,
    /**
     * Deutsche Post Identcode. Requires an 11-digit numerical input. Check digit is
     * calculated.
     */
    DP_IDENTCODE
} public class Code32 extends Symbol {

    private static final char[] TABLE = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F',
        'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
        'W', 'X', 'Y', 'Z'
    };

    @Override
    protected void encode() {
        int i, checksum, checkpart, checkdigit;
        int pharmacode, remainder, devisor;
        String localstr, risultante;
        int[] codeword = new int[6];
        Code3Of9 c39 = new Code3Of9();

        if (content.length() > 8) {
            throw new OkapiException("Input too long");
        }

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

        /* Add leading zeros as required */
        localstr = "";
        for (i = content.length(); i < 8; i++) {
            localstr += "0";
        }
        localstr += content;

        /* Calculate the check digit */
        checksum = 0;
        checkpart = 0;
        for (i = 0; i < 4; i++) {
            checkpart = Character.getNumericValue(localstr.charAt(i * 2));
            checksum += checkpart;
            checkpart = 2 * Character.getNumericValue(localstr.charAt((i * 2) + 1));
            if (checkpart >= 10) {
                checksum += (checkpart - 10) + 1;
            } else {
                checksum += checkpart;
            }
        }

        /* Add check digit to data string */
        checkdigit = checksum % 10;
        char check = (char) (checkdigit + '0');
        localstr += check;
        infoLine("Check Digit: " + check);

        /* Convert string into an integer value */
        pharmacode = 0;
        for (i = 0; i < localstr.length(); i++) {
            pharmacode *= 10;
            pharmacode += Character.getNumericValue(localstr.charAt(i));
        }

        /* Convert from decimal to base-32 */
        devisor = 33554432;
        for (i = 5; i >= 0; i--) {
            codeword[i] = pharmacode / devisor;
            remainder = pharmacode % devisor;
            pharmacode = remainder;
            devisor /= 32;
        }

        /* Look up values in 'Tabella di conversione' */
        risultante = "";
        for (i = 5; i >= 0; i--) {
            risultante += TABLE[codeword[i]];
        }

        /* Plot the barcode using Code 39 */

        readable = "A" + localstr;
        pattern = new String[1];
        row_count = 1;
        row_height = new int[] { -1 };
        infoLine("Code 39 Equivalent: " + risultante);

        c39.setContent(risultante);
        pattern[0] = c39.pattern[0];
    }
}
 public class Code3Of9 extends Symbol {

    public enum CheckDigit {
        NONE, MOD43
    }

    private static final String[] CODE_39 = {
        "1112212111", "2112111121", "1122111121", "2122111111", "1112211121",
        "2112211111", "1122211111", "1112112121", "2112112111", "1122112111",
        "2111121121", "1121121121", "2121121111", "1111221121", "2111221111",
        "1121221111", "1111122121", "2111122111", "1121122111", "1111222111",
        "2111111221", "1121111221", "2121111211", "1111211221", "2111211211",
        "1121211211", "1111112221", "2111112211", "1121112211", "1111212211",
        "2211111121", "1221111121", "2221111111", "1211211121", "2211211111",
        "1221211111", "1211112121", "2211112111", "1221112111", "1212121111",
        "1212111211", "1211121211", "1112121211"
    };

    private static final char[] LOOKUP = {
        '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', '-', '.', ' ', '$',
        '/', '+', '%'
    };

    private CheckDigit checkOption = CheckDigit.NONE;
    private double moduleWidthRatio = 2;

    /**
     * Sets the ratio of wide bar width to narrow bar width. Valid values are usually between
     * {@code 2} and {@code 3}. The default value is {@code 2}.
     *
     * @param moduleWidthRatio the ratio of wide bar width to narrow bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of wide bar width to narrow bar width.
     *
     * @return the ratio of wide bar width to narrow bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    /**
     * Select addition of optional Modulo-43 check digit or encoding without check digit. By
     * default, no check digit is added.
     *
     * @param checkMode Check digit option.
     */
    public void setCheckDigit(CheckDigit checkMode) {
        checkOption = checkMode;
    }

    /**
     * Returns the check digit mode.
     *
     * @return the check digit mode
     */
    public CheckDigit getCheckDigit() {
        return checkOption;
    }

    @Override
    protected void encode() {

        if (!content.matches("[0-9A-Z\\. \\-$/+%]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        String start = "1211212111";
        String stop = "121121211";

        int patternLength = start.length() +
                            stop.length() +
                            (10 * content.length()) +
                            (checkOption == CheckDigit.MOD43 ? 10 : 0);

        StringBuilder dest = new StringBuilder(patternLength);
        dest.append(start);

        int counter = 0;
        char checkDigit = ' ';

        for (int i = 0; i < content.length(); i++) {
            char c = content.charAt(i);
            int index = positionOf(c, LOOKUP);
            dest.append(CODE_39[index]);
            counter += index;
        }

        if (checkOption == CheckDigit.MOD43) {
            counter = counter % 43;
            checkDigit = LOOKUP[counter];
            int index = positionOf(checkDigit, LOOKUP);
            dest.append(CODE_39[index]);
            if (checkDigit == ' ') {
                // display a space check digit as _, otherwise it looks like an error
                checkDigit = '_';
            }
            infoLine("Check Digit: " + checkDigit);
        }

        dest.append(stop);

        if (checkOption == CheckDigit.MOD43) {
            readable = "*" + content + checkDigit + "*";
        } else {
            readable = "*" + content + "*";
        }

        pattern = new String[] { dest.toString() };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    /** {@inheritDoc} */
    @Override
    protected double getModuleWidth(int originalWidth) {
        if (originalWidth == 1) {
            return 1;
        } else {
            return moduleWidthRatio;
        }
    }
}
 public class Code3Of9Extended extends Symbol {

    private static final String[] E_CODE_39 = {
        "%U", "$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", " ", "/A", "/B", "/C",
        "/D", "/E", "/F", "/G", "/H", "/I", "/J", "/K", "/L", "-", ".", "/O",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "/Z", "%F", "%G",
        "%H", "%I", "%J", "%V", "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", "%K", "%L", "%M", "%N", "%O", "%W", "+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", "%P",
        "%Q", "%R", "%S", "%T"
    };

    public enum CheckDigit {
        NONE, MOD43
    }

    private CheckDigit checkOption = CheckDigit.NONE;

    /**
     * Select addition of optional Modulo-43 check digit or encoding without check digit.
     *
     * @param checkMode check digit option
     */
    public void setCheckDigit(CheckDigit checkMode) {
        checkOption = checkMode;
    }

    @Override
    protected void encode() {
        String buffer = "";
        int l = content.length();
        int asciicode;
        Code3Of9 c = new Code3Of9();

        if (checkOption == CheckDigit.MOD43) {
            c.setCheckDigit(Code3Of9.CheckDigit.MOD43);
        }

        if (!content.matches("[\u0000-\u007F]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        for (int i = 0; i < l; i++) {
            asciicode = content.charAt(i);
            buffer += E_CODE_39[asciicode];
        }

        c.setContent(buffer);

        readable = content;
        pattern = new String[1];
        pattern[0] = c.pattern[0];
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }
}
 public class Code49 extends Symbol {

    private static final String[] C49_TABLE7 = {
        /* Table 7: Code 49 ASCII Chart */
        "! ", "!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", "!1", "!2", "!3", "!4", "!5", " ", "!6", "!7", "!8",
        "$", "%", "!9", "!0", "!-", "!.", "!$", "+", "!/", "-", ".", "/", "0",
        "1", "2", "3", "4", "5", "6", "7", "8", "9", "!+", "&1", "&2", "&3",
        "&4", "&5", "&6", "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", "&7", "&8", "&9", "&0", "&-", "&.", "&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", "&$", "&/", "&+",
        "&%", "& "
    };

    /* Table 5: Check Character Weighting Values */
    private static final int[] C49_X_WEIGHT = {
        1, 9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13,
        5, 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10
    };

    private static final int[] C49_Y_WEIGHT = {
        9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5,
        41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24
    };

    private static final int[] C49_Z_WEIGHT = {
        31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, 41,
        33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24, 30
    };

    private static final String[] C49_TABLE4 = {
        /* Table 4: Row Parity Pattern for Code 49 Symbols */
        "OEEO", "EOEO", "OOEE", "EEOO", "OEOE", "EOOE", "OOOO", "EEEE"
    };

    private static final String[] C49_APPXE_EVEN = {
        /* Appendix E - Code 49 Encodation Patterns (Even Symbol Character Parity) */
        /* Column 1 */
        "11521132", "25112131", "14212132", "25121221", "14221222", "12412132",
        "23321221", "12421222", "21521221", "15112222", "15121312", "13312222",
        "24221311", "13321312", "11512222", "22421311", "11521312", "25112311",
        "14212312", "23312311", "12412312", "21512311", "16121131", "14321131",
        "12521131", "15212131", "15221221", "13412131", "13421221", "11612131",
        "16112221", "16121311", "14312221", "14321311", "12512221", "12521311",
        "15212311", "13412311", "11612311", "11131135", "31131133", "51131131",
        "21122134", "41122132", "21131224", "41131222", "11113135", "31113133",
        "51113131", "11122225", "31122223", "51122221", "11131315", "31131313",
        "51131311", "21113224", "41113222", "21122314",
        /* Column 2 */
        "41122312", "11113315", "31113313", "51113311", "12131134", "32131132",
        "21231133", "41231131", "22122133", "42122131", "11222134", "22131223",
        "42131221", "11231224", "31231222", "12113134", "32113132", "12122224",
        "32122222", "12131314", "32131312", "21231313", "41231311", "22113223",
        "42113221", "11213224", "22122313", "42122311", "11222314", "31222312",
        "12113314", "32113312", "21213313", "41213311", "13131133", "33131131",
        "22231132", "11331133", "31331131", "23122132", "12222133", "23131222",
        "12231223", "32231221", "21331222", "13113133", "33113131", "13122223",
        "33122221", "11313133", "13131313", "33131311", "11322223", "22231312",
        "11331313", "31331311", "23113222", "12213223",
        /* Column 3 */
        "23122312", "12222313", "32222311", "21322312", "13113313", "33113311",
        "22213312", "11313313", "31313311", "14131132", "23231131", "12331132",
        "21431131", "24122131", "13222132", "24131221", "13231222", "11422132",
        "22331221", "11431222", "14113132", "14122222", "12313132", "14131312",
        "12322222", "23231311", "12331312", "21431311", "24113221", "13213222",
        "24122311", "13222312", "11413222", "22322311", "11422312", "14113312",
        "23213311", "12313312", "21413311", "15131131", "13331131", "14222131",
        "14231221", "12422131", "12431221", "15113131", "15122221", "13313131",
        "15131311", "13322221", "11513131", "13331311", "11522221", "14213221",
        "14222311", "12413221", "12422311", "15113311",
        /* Column 4 */
        "13313311", "11513311", "11141134", "31141132", "21132133", "41132131",
        "21141223", "41141221", "11123134", "31123132", "11132224", "31132222",
        "11141314", "31141312", "21114133", "41114131", "21123223", "41123221",
        "21132313", "41132311", "11114224", "31114222", "11123314", "31123312",
        "21114313", "41114311", "12141133", "32141131", "21241132", "22132132",
        "11232133", "22141222", "11241223", "31241221", "12123133", "32123131",
        "12132223", "32132221", "12141313", "32141311", "21241312", "22114132",
        "11214133", "22123222", "11223223", "22132312", "11232313", "31232311",
        "12114223", "32114221", "12123313", "32123311", "21223312", "22114312",
        "11214313", "31214311", "13141132", "22241131",
        /* Column 5 */
        "11341132", "23132131", "12232132", "23141221", "12241222", "21341221",
        "13123132", "13132222", "11323132", "13141312", "11332222", "22241311",
        "11341312", "23114131", "12214132", "23123221", "12223222", "23132311",
        "12232312", "21332311", "13114222", "13123312", "11314222", "22223311",
        "11323312", "23114311", "12214312", "21314311", "14141131", "12341131",
        "13232131", "13241221", "11432131", "14123131", "14132221", "12323131",
        "14141311", "12332221", "12341311", "13214131", "13223221", "11414131",
        "13232311", "11423221", "11432311", "14114221", "14123311", "12314221",
        "12323311", "13214311", "11414311", "11151133", "31151131", "21142132",
        "21151222", "11133133", "31133131", "11142223",
        /* Column 6 */
        "31142221", "11151313", "31151311", "21124132", "21133222", "21142312",
        "11115133", "31115131", "11124223", "31124221", "11133313", "31133311",
        "21115222", "21124312", "12151132", "21251131", "22142131", "11242132",
        "22151221", "11251222", "12133132", "12142222", "12151312", "21251311",
        "22124131", "11224132", "22133221", "11233222", "22142311", "11242312",
        "12115132", "12124222", "12133312", "21233311", "22115221", "11215222",
        "22124311", "11224312", "13151131", "12242131", "12251221", "13133131",
        "13142221", "11333131", "13151311", "11342221", "12224131", "12233221",
        "12242311", "13115131", "13124221", "11315131", "13133311", "11324221",
        "11333311", "12215221", "12224311", "11161132",
        /* Column 7 */
        "21152131", "21161221", "11143132", "11152222", "11161312", "21134131",
        "21143221", "21152311", "11125132", "11134222", "11143312", "21116131",
        "21125221", "21134311", "12161131", "11252131", "12143131", "12152221",
        "12161311", "11234131", "11243221", "11252311", "12125131", "12134221",
        "12143311", "11216131", "11225221", "11234311", "11111236", "31111234",
        "51111232", "21111325", "41111323", "61111321", "11111416", "31111414",
        "51111412", "31211143", "51211141", "12111235", "32111233", "52111231",
        "21211234", "41211232", "22111324", "42111322", "11211325", "31211323",
        "51211321", "12111415", "32111413", "52111411", "21211414", "41211412",
        "12211144", "32211142", "21311143", "41311141",
        /* Column 8 */
        "13111234", "33111232", "22211233", "42211231", "11311234", "31311232",
        "23111323", "43111321", "12211324", "32211322", "21311323", "41311321",
        "13111414", "33111412", "22211413", "42211411", "11311414", "31311412",
        "13211143", "33211141", "22311142", "11411143", "31411141", "14111233",
        "34111231", "23211232", "12311233", "32311231", "21411232", "24111322",
        "13211323", "33211321", "22311322", "11411323", "31411321", "14111413",
        "34111411", "23211412", "12311413", "32311411", "21411412", "14211142",
        "23311141", "12411142", "21511141", "15111232", "24211231", "13311232",
        "22411231", "11511232", "25111321", "14211322", "23311321", "12411322",
        "21511321", "15111412", "24211411", "13311412",
        /* Column 9 */
        "22411411", "11511412", "15211141", "13411141", "11611141", "16111231",
        "14311231", "12511231", "15211321", "13411321", "11611321", "16111411",
        "14311411", "12511411", "21121144", "41121142", "11112145", "31112143",
        "51112141", "11121235", "31121233", "51121231", "21112234", "41112232",
        "21121324", "41121322", "11112325", "31112323", "51112321", "11121415",
        "31121413", "51121411", "21112414", "41112412", "22121143", "42121141",
        "11221144", "31221142", "12112144", "32112142", "12121234", "32121232",
        "21221233", "41221231", "22112233", "42112231", "11212234", "22121323",
        "42121321", "11221324", "31221322", "12112324", "32112322", "12121414",
        "32121412", "21221413", "41221411", "22112413",
        /* Column 10 */
        "42112411", "11212414", "31212412", "23121142", "12221143", "32221141",
        "21321142", "13112143", "33112141", "13121233", "33121231", "11312143",
        "22221232", "11321233", "31321231", "23112232", "12212233", "23121322",
        "12221323", "32221321", "21321322", "13112323", "33112321", "13121413",
        "33121411", "11312323", "22221412", "11321413", "31321411", "23112412",
        "12212413", "32212411", "21312412", "24121141", "13221142", "22321141",
        "11421142", "14112142", "14121232", "12312142", "23221231", "12321232",
        "21421231", "24112231", "13212232", "24121321", "13221322", "11412232",
        "22321321", "11421322", "14112322", "14121412", "12312322", "23221411",
        "12321412", "21421411", "24112411", "13212412",
        /* Column 11 */
        "22312411", "11412412", "14221141", "12421141", "15112141", "15121231",
        "13312141", "13321231", "11512141", "11521231", "14212231", "14221321",
        "12412231", "12421321", "15112321", "15121411", "13312321", "13321411",
        "11512321", "11521411", "14212411", "12412411", "21131143", "41131141",
        "11122144", "31122142", "11131234", "31131232", "21113143", "41113141",
        "21122233", "41122231", "21131323", "41131321", "11113234", "31113232",
        "11122324", "31122322", "11131414", "31131412", "21113323", "41113321",
        "21122413", "41122411", "11113414", "31113412", "22131142", "11231143",
        "31231141", "12122143", "32122141", "12131233", "32131231", "21231232",
        "22113142", "11213143", "22122232", "11222233",
        /* Column 12 */
        "22131322", "11231323", "31231321", "12113233", "32113231", "12122323",
        "32122321", "12131413", "32131411", "21231412", "22113322", "11213323",
        "22122412", "11222413", "31222411", "12113413", "32113411", "21213412",
        "23131141", "12231142", "21331141", "13122142", "13131232", "11322142",
        "22231231", "11331232", "23113141", "12213142", "23122231", "12222232",
        "23131321", "12231322", "21331321", "13113232", "13122322", "11313232",
        "13131412", "11322322", "22231411", "11331412", "23113321", "12213322",
        "23122411", "12222412", "21322411", "13113412", "22213411", "11313412",
        "13231141", "11431141", "14122141", "14131231", "12322141", "12331231",
        "13213141", "13222231", "11413141", "13231321",
        /* Column 13 */
        "11422231", "11431321", "14113231", "14122321", "12313231", "14131411",
        "12322321", "12331411", "13213321", "13222411", "11413321", "11422411",
        "14113411", "12313411", "21141142", "11132143", "31132141", "11141233",
        "31141231", "21123142", "21132232", "21141322", "11114143", "31114141",
        "11123233", "31123231", "11132323", "31132321", "11141413", "31141411",
        "21114232", "21123322", "21132412", "11114323", "31114321", "11123413",
        "31123411", "22141141", "11241142", "12132142", "12141232", "21241231",
        "22123141", "11223142", "22132231", "11232232", "22141321", "11241322",
        "12114142", "12123232", "12132322", "12141412", "21241411", "22114231",
        "11214232", "22123321", "11223322", "22132411",
        /* Column 14 */
        "11232412", "12114322", "12123412", "21223411", "12241141", "13132141",
        "13141231", "11332141", "11341231", "12223141", "12232231", "12241321",
        "13114141", "13123231", "11314141", "13132321", "11323231", "13141411",
        "11332321", "11341411", "12214231", "12223321", "12232411", "13114321",
        "13123411", "11314321", "11323411", "21151141", "11142142", "11151232",
        "21133141", "21142231", "21151321", "11124142", "11133232", "11142322",
        "11151412", "21115141", "21124231", "21133321", "21142411", "11115232",
        "11124322", "11133412", "11251141", "12142141", "12151231", "11233141",
        "11242231", "11251321", "12124141", "12133231", "12142321", "12151411",
        "11215141", "11224231", "11233321", "11242411",
        /* Column 15 */
        "12115231", "12124321", "12133411", "11152141", "11161231", "11134141",
        "11143231", "11152321", "11161411", "11116141", "11125231", "11134321",
        "11143411", "21111244", "41111242", "11111335", "31111333", "51111331",
        "21111424", "41111422", "11111515", "31111513", "51111511", "21211153",
        "41211151", "22111243", "42111241", "11211244", "31211242", "12111334",
        "32111332", "21211333", "41211331", "22111423", "42111421", "11211424",
        "31211422", "12111514", "32111512", "21211513", "41211511", "22211152",
        "11311153", "31311151", "23111242", "12211243", "32211241", "21311242",
        "13111333", "33111331", "22211332", "11311333", "31311331", "23111422",
        "12211423", "32211421", "21311422", "13111513",
        /* Column 16 */
        "33111511", "22211512", "11311513", "31311511", "23211151", "12311152",
        "21411151", "24111241", "13211242", "22311241", "11411242", "14111332",
        "23211331", "12311332", "21411331", "24111421", "13211422", "22311421",
        "11411422", "14111512", "23211511", "12311512", "21411511", "13311151",
        "11511151", "14211241", "12411241", "15111331", "13311331", "11511331",
        "14211421", "12411421", "15111511", "13311511", "11511511", "31121152",
        "21112153", "41112151", "21121243", "41121241", "11112244", "31112242",
        "11121334", "31121332", "21112333", "41112331", "21121423", "41121421",
        "11112424", "31112422", "11121514", "31121512", "21112513", "41112511",
        "12121153", "32121151", "21221152", "22112152",
        /* Column 17 */
        "11212153", "22121242", "11221243", "31221241", "12112243", "32112241",
        "12121333", "32121331", "21221332", "22112332", "11212333", "22121422",
        "11221423", "31221421", "12112423", "32112421", "12121513", "32121511",
        "21221512", "22112512", "11212513", "31212511", "13121152", "22221151",
        "11321152", "23112151", "12212152", "23121241", "12221242", "21321241",
        "13112242", "13121332", "11312242", "22221331", "11321332", "23112331",
        "12212332", "23121421", "12221422", "21321421", "13112422", "13121512",
        "11312422", "22221511", "11321512", "23112511", "12212512", "21312511",
        "14121151", "12321151", "13212151", "13221241", "11412151", "11421241",
        "14112241", "14121331", "12312241", "12321331",
        /* Column 18 */
        "13212331", "13221421", "11412331", "11421421", "14112421", "14121511",
        "12312421", "12321511", "13212511", "11412511", "11131153", "31131151",
        "21122152", "21131242", "11113153", "31113151", "11122243", "31122241",
        "11131333", "31131331", "21113242", "21122332", "21131422", "11113333",
        "31113331", "11122423", "31122421", "11131513", "31131511", "21113422",
        "21122512", "12131152", "21231151", "22122151", "11222152", "22131241",
        "11231242", "12113152", "12122242", "12131332", "21231331", "22113241",
        "11213242", "22122331", "11222332", "22131421", "11231422", "12113332",
        "12122422", "12131512", "21231511", "22113421", "11213422", "22122511",
        "11222512", "13131151", "11331151", "12222151",
        /* Column 19 */
        "12231241", "13113151", "13122241", "11313151", "13131331", "11322241",
        "11331331", "12213241", "12222331", "12231421", "13113331", "13122421",
        "11313331", "13131511", "11322421", "11331511", "12213421", "12222511",
        "11141152", "21132151", "21141241", "11123152", "11132242", "11141332",
        "21114151", "21123241", "21132331", "21141421", "11114242", "11123332",
        "11132422", "11141512", "21114331", "21123421", "21132511", "12141151",
        "11232151", "11241241", "12123151", "12132241", "12141331", "11214151",
        "11223241", "11232331", "11241421", "12114241", "12123331", "12132421",
        "12141511", "11214331", "11223421", "11232511", "11151151", "11133151",
        "11142241", "11151331", "11115151", "11124241",
        /* Column 20 */
        "11133331", "11142421", "11151511", "11111254", "31111252", "21111343",
        "41111341", "11111434", "31111432", "21111523", "41111521", "11111614",
        "31111612", "31211161", "12111253", "32111251", "21211252", "22111342",
        "11211343", "31211341", "12111433", "32111431", "21211432", "22111522",
        "11211523", "31211521", "12111613", "32111611", "21211612", "12211162",
        "21311161", "13111252", "22211251", "11311252", "23111341", "12211342",
        "21311341", "13111432", "22211431", "11311432", "23111521", "12211522",
        "21311521", "13111612", "22211611", "11311612", "13211161", "11411161",
        "14111251", "12311251", "13211341", "11411341", "14111431", "12311431",
        "13211521", "11411521", "14111611", "12311611",
        /* Column 21 */
        "21121162", "11112163", "31112161", "11121253", "31121251", "21112252",
        "21121342", "11112343", "31112341", "11121433", "31121431", "21112432",
        "21121522", "11112523", "31112521", "11121613", "31121611", "22121161",
        "11221162", "12112162", "12121252", "21221251", "22112251", "11212252",
        "22121341", "11221342", "12112342", "12121432", "21221431", "22112431",
        "11212432", "22121521", "11221522", "12112522", "12121612", "21221611",
        "12221161", "13112161", "13121251", "11312161", "11321251", "32121115",
        "52121113", "21221116", "41221114", "61221112", "22112116", "42112114",
        "31212115", "51212113", "13121116", "33121114", "22221115", "42221113",
        "11321116", "31321114", "51321112", "23112115",
        /* Column 22 */
        "43112113", "12212116", "32212114", "52212112", "21312115", "41312113",
        "61312111", "14121115", "34121113", "23221114", "43221112", "12321115",
        "32321113", "52321111", "21421114", "41421112", "24112114", "13212115",
        "33212113", "22312114", "42312112", "11412115", "31412113", "51412111",
        "15121114", "24221113", "13321114", "33321112", "22421113", "42421111",
        "11521114", "31521112", "25112113", "14212114", "34212112", "23312113",
        "43312111", "12412114", "32412112", "21512113", "41512111", "16121113",
        "25221112", "14321113", "34321111", "23421112", "12521113", "32521111",
        "15212113", "24312112", "13412113", "33412111", "22512112", "11612113",
        "31612111", "31131115", "51131113", "21122116",
        /* Column 23 */
        "41122114", "61122112", "31113115", "51113113", "12131116", "32131114",
        "52131112", "21231115", "41231113", "61231111", "22122115", "42122113",
        "11222116", "31222114", "51222112", "12113116", "32113114", "52113112",
        "21213115", "41213113", "61213111", "13131115", "33131113", "22231114",
        "42231112", "11331115", "31331113", "51331111", "23122114", "43122112",
        "12222115", "32222113", "52222111", "21322114", "41322112", "13113115",
        "33113113", "22213114", "42213112", "11313115", "31313113", "51313111",
        "14131114", "34131112", "23231113", "43231111", "12331114", "32331112",
        "21431113", "41431111", "24122113", "13222114", "33222112", "22322113",
        "42322111", "11422114", "31422112", "14113114",
        /* Column 24 */
        "34113112", "23213113", "43213111", "12313114", "32313112", "21413113",
        "41413111", "15131113", "24231112", "13331113", "33331111", "22431112",
        "25122112", "14222113", "34222111", "23322112", "12422113", "32422111",
        "21522112", "15113113", "24213112", "13313113", "33313111", "22413112",
        "11513113", "31513111", "16131112", "25231111", "14331112", "23431111",
        "15222112", "24322111", "13422112", "22522111", "16113112", "25213111",
        "14313112", "23413111", "12513112", "21613111", "11141116", "31141114",
        "51141112", "21132115", "41132113", "61132111", "11123116", "31123114",
        "51123112", "21114115", "41114113", "61114111", "12141115", "32141113",
        "52141111", "21241114", "41241112", "22132114",
        /* Column 25 */
        "42132112", "11232115", "31232113", "51232111", "12123115", "32123113",
        "52123111", "21223114", "41223112", "22114114", "42114112", "11214115",
        "31214113", "51214111", "13141114", "33141112", "22241113", "42241111",
        "11341114", "31341112", "23132113", "43132111", "12232114", "32232112",
        "21332113", "41332111", "13123114", "33123112", "22223113", "42223111",
        "11323114", "31323112", "23114113", "43114111", "12214114", "32214112",
        "21314113", "41314111", "14141113", "34141111", "23241112", "12341113",
        "32341111", "24132112", "13232113", "33232111", "22332112", "11432113",
        "31432111", "14123113", "34123111", "23223112", "12323113", "32323111",
        "21423112", "24114112", "13214113", "33214111",
        /* Column 26 */
        "22314112", "11414113", "31414111", "15141112", "24241111", "13341112",
        "25132111", "14232112", "23332111", "12432112", "15123112", "24223111",
        "13323112", "22423111", "11523112", "25114111", "14214112", "23314111",
        "12414112", "21514111", "16141111", "14341111", "15232111", "13432111",
        "16123111", "14323111", "12523111", "15214111", "13414111", "11614111",
        "11151115", "31151113", "51151111", "21142114", "41142112", "11133115",
        "31133113", "51133111", "21124114", "41124112", "11115115", "31115113",
        "51115111", "12151114", "32151112", "21251113", "41251111", "22142113",
        "42142111", "11242114", "31242112", "12133114", "32133112", "21233113",
        "41233111", "22124113", "42124111", "11224114",
        /* Column 27 */
        "31224112", "12115114", "32115112", "21215113", "41215111", "13151113",
        "33151111", "22251112", "23142112", "12242113", "32242111", "21342112",
        "13133113", "33133111", "22233112", "11333113", "31333111", "23124112",
        "12224113", "32224111", "21324112", "13115113", "33115111", "22215112",
        "11315113", "31315111", "14151112", "23251111", "24142111", "13242112",
        "22342111", "14133112", "23233111", "12333112", "21433111", "24124111",
        "13224112", "22324111", "11424112", "14115112", "23215111", "12315112",
        "21415111", "15151111", "14242111", "15133111", "13333111", "14224111",
        "12424111", "15115111", "13315111", "11515111", "11161114", "31161112",
        "21152113", "41152111", "11143114", "31143112",
        /* Column 28 */
        "21134113", "41134111", "11125114", "31125112", "21116113", "41116111",
        "12161113", "32161111", "22152112", "11252113", "31252111", "12143113",
        "32143111", "21243112", "22134112", "11234113", "31234111", "12125113",
        "32125111", "21225112", "22116112", "11216113", "31216111", "13161112",
        "23152111", "12252112", "13143112", "22243111", "11343112", "23134111",
        "12234112", "21334111", "13125112", "22225111", "11325112", "23116111",
        "12216112", "21316111", "14161111", "13252111", "14143111", "12343111",
        "13234111", "11434111", "14125111", "12325111", "13216111", "11416111",
        "31111216", "51111214", "31211125", "51211123", "32111215", "52111213",
        "21211216", "41211214", "61211212", "12211126",
        /* Column 29 */
        "32211124", "52211122", "21311125", "41311123", "61311121", "13111216",
        "33111214", "22211215", "42211213", "11311216", "31311214", "51311212",
        "13211125", "33211123", "22311124", "42311122", "11411125", "31411123",
        "51411121", "14111215", "34111213", "23211214", "43211212", "12311215",
        "32311213", "52311211", "21411214", "41411212", "14211124", "34211122",
        "23311123", "43311121", "12411124", "32411122", "21511123", "41511121",
        "15111214", "24211213", "13311214", "33311212", "22411213", "42411211",
        "11511214", "31511212", "15211123", "24311122", "13411123", "33411121",
        "22511122", "11611123", "31611121", "16111213", "25211212", "14311213",
        "34311211", "23411212", "12511213", "32511211",
        /* Column 30 */
        "21611212", "21121126", "41121124", "61121122", "31112125", "51112123",
        "31121215", "51121213", "21112216", "41112214", "61112212", "22121125",
        "42121123", "11221126", "31221124", "51221122", "12112126", "32112124",
        "52112122", "12121216", "32121214", "52121212", "21221215", "41221213",
        "61221211", "22112215", "42112213", "11212216", "31212214", "51212212",
        "23121124", "43121122", "12221125", "32221123", "52221121", "21321124",
        "41321122", "13112125", "33112123", "13121215", "33121213", "11312125",
        "22221214", "42221212", "11321215", "31321213", "51321211", "23112214",
        "43112212", "12212215", "32212213", "52212211", "21312214", "41312212",
        "24121123", "13221124", "33221122", "22321123",
        /* Column 31 */
        "42321121", "11421124", "31421122", "14112124", "34112122", "14121214",
        "34121212", "12312124", "23221213", "43221211", "12321214", "32321212",
        "21421213", "41421211", "24112213", "13212214", "33212212", "22312213",
        "42312211", "11412214", "31412212", "25121122", "14221123", "34221121",
        "23321122", "12421123", "32421121", "21521122", "15112123", "15121213",
        "13312123", "24221212", "13321213", "33321211", "11512123", "22421212",
        "11521213", "31521211", "25112212", "14212213", "34212211", "23312212",
        "12412213", "32412211", "21512212", "15221122", "24321121", "13421122",
        "22521121", "16112122", "16121212", "14312122", "25221211", "14321212",
        "12512122", "23421211", "12521212", "15212212",
        /* Column 32 */
        "24312211", "13412212", "22512211", "11612212", "21131125", "41131123",
        "61131121", "11122126", "31122124", "51122122", "11131216", "31131214",
        "51131212", "21113125", "41113123", "61113121", "21122215", "41122213",
        "61122211", "11113216", "31113214", "51113212", "22131124", "42131122",
        "11231125", "31231123", "51231121", "12122125", "32122123", "52122121",
        "12131215", "32131213", "52131211", "21231214", "41231212", "22113124",
        "42113122", "11213125", "22122214", "42122212", "11222215", "31222213",
        "51222211", "12113215", "32113213", "52113211", "21213214", "41213212",
        "23131123", "43131121", "12231124", "32231122", "21331123", "41331121",
        "13122124", "33122122", "13131214", "33131212",
        /* Column 33 */
        "11322124", "22231213", "42231211", "11331214", "31331212", "23113123",
        "43113121", "12213124", "23122213", "43122211", "12222214", "32222212",
        "21322213", "41322211", "13113214", "33113212", "22213213", "42213211",
        "11313214", "31313212", "24131122", "13231123", "33231121", "22331122",
        "11431123", "31431121", "14122123", "34122121", "14131213", "34131211",
        "12322123", "23231212", "12331213", "32331211", "21431212", "24113122",
        "13213123", "24122212", "13222213", "33222211", "11413123", "22322212",
        "11422213", "31422211", "14113213", "34113211", "23213212", "12313213",
        "32313211", "21413212", "25131121", "14231122", "23331121", "12431122",
        "15122122", "15131212", "13322122", "24231211",
        /* Column 34 */
        "13331212", "11522122", "22431211", "25113121", "14213122", "25122211",
        "14222212", "12413122", "23322211", "12422212", "21522211", "15113212",
        "24213211", "13313212", "22413211", "11513212", "15231121", "13431121",
        "16122121", "16131211", "14322121", "14331211", "12522121", "15213121",
        "15222211", "13413121", "13422211", "11613121", "16113211", "14313211",
        "12513211", "21141124", "41141122", "11132125", "31132123", "51132121",
        "11141215", "31141213", "51141211", "21123124", "41123122", "21132214",
        "41132212", "11114125", "31114123", "51114121", "11123215", "31123213",
        "51123211", "21114214", "41114212", "22141123", "42141121", "11241124",
        "31241122", "12132124", "32132122", "12141214",
        /* Column 35 */
        "32141212", "21241213", "41241211", "22123123", "42123121", "11223124",
        "22132213", "42132211", "11232214", "31232212", "12114124", "32114122",
        "12123214", "32123212", "21223213", "41223211", "22114213", "42114211",
        "11214214", "31214212", "23141122", "12241123", "32241121", "21341122",
        "13132123", "33132121", "13141213", "33141211", "11332123", "22241212",
        "11341213", "31341211", "23123122", "12223123", "23132212", "12232213",
        "32232211", "21332212", "13114123", "33114121", "13123213", "33123211",
        "11314123", "22223212", "11323213", "31323211", "23114212", "12214213",
        "32214211", "21314212", "24141121", "13241122", "22341121", "14132122",
        "14141212", "12332122", "23241211", "12341212",
        /* Column 36 */
        "24123121", "13223122", "24132211", "13232212", "11423122", "22332211",
        "11432212", "14114122", "14123212", "12314122", "23223211", "12323212",
        "21423211", "24114211", "13214212", "22314211", "11414212", "14241121",
        "15132121", "15141211", "13332121", "13341211", "14223121", "14232211",
        "12423121", "12432211", "15114121", "15123211", "13314121", "13323211",
        "11514121", "11523211", "14214211", "12414211", "21151123", "41151121",
        "11142124", "31142122", "11151214", "31151212", "21133123", "41133121",
        "21142213", "41142211", "11124124", "31124122", "11133214", "31133212",
        "21115123", "41115121", "21124213", "41124211", "11115214", "31115212",
        "22151122", "11251123", "31251121", "12142123",
        /* Column 37 */
        "32142121", "12151213", "32151211", "21251212", "22133122", "11233123",
        "22142212", "11242213", "31242211", "12124123", "32124121", "12133213",
        "32133211", "21233212", "22115122", "11215123", "22124212", "11224213",
        "31224211", "12115213", "32115211", "21215212", "23151121", "12251122",
        "13142122", "13151212", "11342122", "22251211", "23133121", "12233122",
        "23142211", "12242212", "21342211", "13124122", "13133212", "11324122",
        "22233211", "11333212", "23115121", "12215122", "23124211", "12224212",
        "21324211", "13115212", "22215211", "11315212", "13251121", "14142121",
        "14151211", "12342121", "13233121", "13242211", "11433121", "14124121",
        "14133211", "12324121", "12333211", "13215121",
        /* Column 38 */
        "13224211", "11415121", "11424211", "14115211", "12315211", "21161122",
        "11152123", "31152121", "11161213", "31161211", "21143122", "21152212",
        "11134123", "31134121", "11143213", "31143211", "21125122", "21134212",
        "11116123", "31116121", "11125213", "31125211", "22161121", "12152122",
        "12161212", "22143121", "11243122", "22152211", "11252212", "12134122",
        "12143212", "21243211", "22125121", "11225122", "22134211", "11234212",
        "12116122", "12125212", "21225211", "13152121", "13161211", "12243121",
        "12252211", "13134121", "13143211", "11334121", "11343211", "12225121",
        "12234211", "13116121", "13125211", "11316121", "11325211", "21111226",
        "41111224", "61111222", "31111315", "51111313",
        /* Column 39 */
        "21211135", "41211133", "61211131", "22111225", "42111223", "11211226",
        "31211224", "51211222", "12111316", "32111314", "52111312", "21211315",
        "41211313", "61211311", "22211134", "42211132", "11311135", "31311133",
        "51311131", "23111224", "43111222", "12211225", "32211223", "52211221",
        "21311224", "41311222", "13111315", "33111313", "22211314", "42211312",
        "11311315", "31311313", "51311311", "23211133", "43211131", "12311134",
        "32311132", "21411133", "41411131", "24111223", "13211224", "33211222",
        "22311223", "42311221", "11411224", "31411222", "14111314", "34111312",
        "23211313", "43211311", "12311314", "32311312", "21411313", "41411311",
        "24211132", "13311133", "33311131", "22411132",
        /* Column 40 */
        "11511133", "31511131", "25111222", "14211223", "34211221", "23311222",
        "12411223", "32411221", "21511222", "15111313", "24211312", "13311313",
        "33311311", "22411312", "11511313", "31511311", "25211131", "14311132",
        "23411131", "12511132", "21611131", "15211222", "24311221", "13411222",
        "22511221", "11611222", "16111312", "25211311", "14311312", "23411311",
        "12511312", "21611311", "31121134", "51121132", "21112135", "41112133",
        "61112131", "21121225", "41121223", "61121221", "11112226", "31112224",
        "51112222", "11121316", "31121314", "51121312", "21112315", "41112313",
        "61112311", "12121135", "32121133", "52121131", "21221134", "41221132",
        "22112134", "42112132", "11212135", "22121224",
        /* Column 41 */
        "42121222", "11221225", "31221223", "51221221", "12112225", "32112223",
        "52112221", "12121315", "32121313", "52121311", "21221314", "41221312",
        "22112314", "42112312", "11212315", "31212313", "51212311", "13121134",
        "33121132", "22221133", "42221131", "11321134", "31321132", "23112133",
        "43112131", "12212134", "23121223", "43121221", "12221224", "32221222",
        "21321223", "41321221", "13112224", "33112222", "13121314", "33121312",
        "11312224", "22221313", "42221311", "11321314", "31321312",
        /* Column 42 */
        "23112313", "43112311", "12212314", "32212312", "21312313", "41312311",
        "14121133", "34121131", "23221132", "12321133", "32321131", "21421132",
        "24112132", "13212133", "24121222", "13221223", "33221221", "11412133",
        "22321222", "11421223", "31421221", "14112223", "34112221", "14121313",
        "34121311", "12312223", "23221312", "12321313", "32321311", "21421312",
        "24112312", "13212313", "33212311", "22312312", "11412313", "31412311",
        "15121132", "24221131", "13321132", "22421131"
    };

    private static final String[] C49_APPXE_ODD = {
        /* Appendix E - Code 49 Encodation Patterns (Odd Symbol Character Parity) */
        /* Column 1 */
        "22121116", "42121114", "31221115", "51221113", "32112115", "52112113",
        "21212116", "41212114", "61212112", "23121115", "43121113", "12221116",
        "32221114", "52221112", "21321115", "41321113", "61321111", "13112116",
        "33112114", "22212115", "42212113", "11312116", "31312114", "51312112",
        "24121114", "13221115", "33221113", "22321114", "42321112", "11421115",
        "31421113", "51421111", "14112115", "34112113", "23212114", "43212112",
        "12312115", "32312113", "52312111", "21412114", "41412112", "25121113",
        "14221114", "34221112", "23321113", "43321111", "12421114", "32421112",
        "21521113", "41521111", "15112114", "24212113", "13312114", "33312112",
        "22412113", "42412111", "11512114", "31512112",
        /* Column 2 */
        "15221113", "24321112", "13421113", "33421111", "22521112", "16112113",
        "25212112", "14312113", "34312111", "23412112", "12512113", "32512111",
        "21612112", "21131116", "41131114", "61131112", "31122115", "51122113",
        "21113116", "41113114", "61113112", "22131115", "42131113", "11231116",
        "31231114", "51231112", "12122116", "32122114", "52122112", "21222115",
        "41222113", "61222111", "22113115", "42113113", "11213116", "31213114",
        "51213112", "23131114", "43131112", "12231115", "32231113", "52231111",
        "21331114", "41331112", "13122115", "33122113", "22222114", "42222112",
        "11322115", "31322113", "51322111", "23113114", "43113112", "12213115",
        "32213113", "52213111", "21313114", "41313112",
        /* Column 3 */
        "24131113", "13231114", "33231112", "22331113", "42331111", "11431114",
        "31431112", "14122114", "34122112", "23222113", "43222111", "12322114",
        "32322112", "21422113", "41422111", "24113113", "13213114", "33213112",
        "22313113", "42313111", "11413114", "31413112", "25131112", "14231113",
        "34231111", "23331112", "12431113", "32431111", "15122113", "24222112",
        "13322113", "33322111", "22422112", "11522113", "31522111", "25113112",
        "14213113", "34213111", "23313112", "12413113", "32413111", "21513112",
        "15231112", "24331111", "13431112", "16122112", "25222111", "14322112",
        "23422111", "12522112", "15213112", "24313111", "13413112", "22513111",
        "11613112", "21141115", "41141113", "61141111",
        /* Column 4 */
        "11132116", "31132114", "51132112", "21123115", "41123113", "61123111",
        "11114116", "31114114", "51114112", "22141114", "42141112", "11241115",
        "31241113", "51241111", "12132115", "32132113", "52132111", "21232114",
        "41232112", "22123114", "42123112", "11223115", "31223113", "51223111",
        "12114115", "32114113", "52114111", "21214114", "41214112", "23141113",
        "43141111", "12241114", "32241112", "21341113", "41341111", "13132114",
        "33132112", "22232113", "42232111", "11332114", "31332112", "23123113",
        "43123111", "12223114", "32223112", "21323113", "41323111", "13114114",
        "33114112", "22214113", "42214111", "11314114", "31314112", "24141112",
        "13241113", "33241111", "22341112", "14132113",
        /* Column 5 */
        "34132111", "23232112", "12332113", "32332111", "21432112", "24123112",
        "13223113", "33223111", "22323112", "11423113", "31423111", "14114113",
        "34114111", "23214112", "12314113", "32314111", "21414112", "25141111",
        "14241112", "23341111", "15132112", "24232111", "13332112", "22432111",
        "25123111", "14223112", "23323111", "12423112", "21523111", "15114112",
        "24214111", "13314112", "22414111", "11514112", "15241111", "16132111",
        "14332111", "15223111", "13423111", "16114111", "14314111", "12514111",
        "21151114", "41151112", "11142115", "31142113", "51142111", "21133114",
        "41133112", "11124115", "31124113", "51124111", "21115114", "41115112",
        "22151113", "42151111", "11251114", "31251112",
        /* Column 6 */
        "12142114", "32142112", "21242113", "41242111", "22133113", "42133111",
        "11233114", "31233112", "12124114", "32124112", "21224113", "41224111",
        "22115113", "42115111", "11215114", "31215112", "23151112", "12251113",
        "32251111", "13142113", "33142111", "22242112", "11342113", "31342111",
        "23133112", "12233113", "32233111", "21333112", "13124113", "33124111",
        "22224112", "11324113", "31324111", "23115112", "12215113", "32215111",
        "21315112", "24151111", "13251112", "14142112", "23242111", "12342112",
        "24133111", "13233112", "22333111", "11433112", "14124112", "23224111",
        "12324112", "21424111", "24115111", "13215112", "22315111", "11415112",
        "14251111", "15142111", "13342111", "14233111",
        /* Column 7 */
        "12433111", "15124111", "13324111", "11524111", "14215111", "12415111",
        "21161113", "41161111", "11152114", "31152112", "21143113", "41143111",
        "11134114", "31134112", "21125113", "41125111", "11116114", "31116112",
        "22161112", "12152113", "32152111", "21252112", "22143112", "11243113",
        "31243111", "12134113", "32134111", "21234112", "22125112", "11225113",
        "31225111", "12116113", "32116111", "21216112", "23161111", "13152112",
        "22252111", "23143111", "12243112", "21343111", "13134112", "22234111",
        "11334112", "23125111", "12225112", "21325111", "13116112", "22216111",
        "11316112", "14152111", "13243111", "14134111", "12334111", "13225111",
        "11425111", "14116111", "12316111", "41111215",
        /* Column 8 */
        "61111213", "21211126", "41211124", "61211122", "22111216", "42111214",
        "31211215", "51211213", "22211125", "42211123", "11311126", "31311124",
        "51311122", "23111215", "43111213", "12211216", "32211214", "52211212",
        "21311215", "41311213", "61311211", "23211124", "43211122", "12311125",
        "32311123", "52311121", "21411124", "41411122", "24111214", "13211215",
        "33211213", "22311214", "42311212", "11411215", "31411213", "51411211",
        "24211123", "13311124", "33311122", "22411123", "42411121", "11511124",
        "31511122", "25111213", "14211214", "34211212", "23311213", "43311211",
        "12411214", "32411212", "21511213", "41511211", "25211122", "14311123",
        "34311121", "23411122", "12511123", "32511121",
        /* Column 9 */
        "21611122", "15211213", "24311212", "13411213", "33411211", "22511212",
        "11611213", "31611211", "31121125", "51121123", "21112126", "41112124",
        "61112122", "21121216", "41121214", "61121212", "31112215", "51112213",
        "12121126", "32121124", "52121122", "21221125", "41221123", "61221121",
        "22112125", "42112123", "11212126", "22121215", "42121213", "11221216",
        "31221214", "51221212", "12112216", "32112214", "52112212", "21212215",
        "41212213", "61212211", "13121125", "33121123", "22221124", "42221122",
        "11321125", "31321123", "51321121", "23112124", "43112122", "12212125",
        "23121214", "43121212", "12221215", "32221213", "52221211", "21321214",
        "41321212", "13112215", "33112213", "22212214",
        /* Column 10 */
        "42212212", "11312215", "31312213", "51312211", "14121124", "34121122",
        "23221123", "43221121", "12321124", "32321122", "21421123", "41421121",
        "24112123", "13212124", "24121213", "13221214", "33221212", "11412124",
        "22321213", "42321211", "11421214", "31421212", "14112214", "34112212",
        "23212213", "43212211", "12312214", "32312212", "21412213", "41412211",
        "15121123", "24221122", "13321123", "33321121", "22421122", "11521123",
        "31521121", "25112122", "14212123", "25121212", "14221213", "34221211",
        "12412123", "23321212", "12421213", "32421211", "21521212", "15112213",
        "24212212", "13312213", "33312211", "22412212", "11512213", "31512211",
        "16121122", "25221121", "14321122", "23421121",
        /* Column 11 */
        "12521122", "15212122", "15221212", "13412122", "24321211", "13421212",
        "11612122", "22521211", "16112212", "25212211", "14312212", "23412211",
        "12512212", "21612211", "11131126", "31131124", "51131122", "21122125",
        "41122123", "61122121", "21131215", "41131213", "61131211", "11113126",
        "31113124", "51113122", "11122216", "31122214", "51122212", "21113215",
        "41113213", "61113211", "12131125", "32131123", "52131121", "21231124",
        "41231122", "22122124", "42122122", "11222125", "22131214", "42131212",
        "11231215", "31231213", "51231211", "12113125", "32113123", "52113121",
        "12122215", "32122213", "52122211", "21222214", "41222212", "22113214",
        "42113212", "11213215", "31213213", "51213211",
        /* Column 12 */
        "13131124", "33131122", "22231123", "42231121", "11331124", "31331122",
        "23122123", "43122121", "12222124", "23131213", "43131211", "12231214",
        "32231212", "21331213", "41331211", "13113124", "33113122", "13122214",
        "33122212", "11313124", "22222213", "42222211", "11322214", "31322212",
        "23113213", "43113211", "12213214", "32213212", "21313213", "41313211",
        "14131123", "34131121", "23231122", "12331123", "32331121", "21431122",
        "24122122", "13222123", "24131212", "13231213", "33231211", "11422123",
        "22331212", "11431213", "31431211", "14113123", "34113121", "14122213",
        "34122211", "12313123", "23222212", "12322213", "32322211", "21422212",
        "24113212", "13213213", "33213211", "22313212",
        /* Column 13 */
        "11413213", "31413211", "15131122", "24231121", "13331122", "22431121",
        "25122121", "14222122", "25131211", "14231212", "12422122", "23331211",
        "12431212", "15113122", "15122212", "13313122", "24222211", "13322212",
        "11513122", "22422211", "11522212", "25113211", "14213212", "23313211",
        "12413212", "21513211", "16131121", "14331121", "15222121", "15231211",
        "13422121", "13431211", "16113121", "16122211", "14313121", "14322211",
        "12513121", "12522211", "15213211", "13413211", "11613211", "11141125",
        "31141123", "51141121", "21132124", "41132122", "21141214", "41141212",
        "11123125", "31123123", "51123121", "11132215", "31132213", "51132211",
        "21114124", "41114122", "21123214", "41123212",
        /* Column 14 */
        "11114215", "31114213", "51114211", "12141124", "32141122", "21241123",
        "41241121", "22132123", "42132121", "11232124", "22141213", "42141211",
        "11241214", "31241212", "12123124", "32123122", "12132214", "32132212",
        "21232213", "41232211", "22114123", "42114121", "11214124", "22123213",
        "42123211", "11223214", "31223212", "12114214", "32114212", "21214213",
        "41214211", "13141123", "33141121", "22241122", "11341123", "31341121",
        "23132122", "12232123", "23141212", "12241213", "32241211", "21341212",
        "13123123", "33123121", "13132213", "33132211", "11323123", "22232212",
        "11332213", "31332211", "23114122", "12214123", "23123212", "12223213",
        "32223211", "21323212", "13114213", "33114211",
        /* Column 15 */
        "22214212", "11314213", "31314211", "14141122", "23241121", "12341122",
        "24132121", "13232122", "24141211", "13241212", "11432122", "22341211",
        "14123122", "14132212", "12323122", "23232211", "12332212", "21432211",
        "24114121", "13214122", "24123211", "13223212", "11414122", "22323211",
        "11423212", "14114212", "23214211", "12314212", "21414211", "15141121",
        "13341121", "14232121", "14241211", "12432121", "15123121", "15132211",
        "13323121", "13332211", "11523121", "14214121", "14223211", "12414121",
        "12423211", "15114211", "13314211", "11514211", "11151124", "31151122",
        "21142123", "41142121", "21151213", "41151211", "11133124", "31133122",
        "11142214", "31142212", "21124123", "41124121",
        /* Column 16 */
        "21133213", "41133211", "11115124", "31115122", "11124214", "31124212",
        "21115213", "41115211", "12151123", "32151121", "21251122", "22142122",
        "11242123", "22151212", "11251213", "31251211", "12133123", "32133121",
        "12142213", "32142211", "21242212", "22124122", "11224123", "22133212",
        "11233213", "31233211", "12115123", "32115121", "12124213", "32124211",
        "21224212", "22115212", "11215213", "31215211", "13151122", "22251121",
        "23142121", "12242122", "23151211", "12251212", "13133122", "13142212",
        "11333122", "22242211", "11342212", "23124121", "12224122", "23133211",
        "12233212", "21333211", "13115122", "13124212", "11315122", "22224211",
        "11324212", "23115211", "12215212", "21315211",
        /* Column 17 */
        "14151121", "13242121", "13251211", "14133121", "14142211", "12333121",
        "12342211", "13224121", "13233211", "11424121", "11433211", "14115121",
        "14124211", "12315121", "12324211", "13215211", "11415211", "11161123",
        "31161121", "21152122", "21161212", "11143123", "31143121", "11152213",
        "31152211", "21134122", "21143212", "11125123", "31125121", "11134213",
        "31134211", "21116122", "21125212", "12161122", "22152121", "11252122",
        "22161211", "12143122", "12152212", "21252211", "22134121", "11234122",
        "22143211", "11243212", "12125122", "12134212", "21234211", "22116121",
        "11216122", "22125211", "11225212", "13161121", "12252121", "13143121",
        "13152211", "11343121", "12234121", "12243211",
        /* Column 18 */
        "13125121", "13134211", "11325121", "11334211", "12216121", "12225211",
        "31111225", "51111223", "21111316", "41111314", "61111312", "31211134",
        "51211132", "12111226", "32111224", "52111222", "21211225", "41211223",
        "61211221", "22111315", "42111313", "11211316", "31211314", "51211312",
        "12211135", "32211133", "52211131", "21311134", "41311132", "13111225",
        "33111223", "22211224", "42211222", "11311225", "31311223", "51311221",
        "23111314", "43111312", "12211315", "32211313", "52211311", "21311314",
        "41311312", "13211134", "33211132", "22311133", "42311131", "11411134",
        "31411132", "14111224", "34111222", "23211223", "43211221", "12311224",
        "32311222", "21411223", "41411221", "24111313",
        /* Column 19 */
        "13211314", "33211312", "22311313", "42311311", "11411314", "31411312",
        "14211133", "34211131", "23311132", "12411133", "32411131", "21511132",
        "15111223", "24211222", "13311223", "33311221", "22411222", "11511223",
        "31511221", "25111312", "14211313", "34211311", "23311312", "12411313",
        "32411311", "21511312", "15211132", "24311131", "13411132", "22511131",
        "11611132", "16111222", "25211221", "14311222", "23411221", "12511222",
        "21611221", "15211312", "24311311", "13411312", "22511311", "11611312",
        "21121135", "41121133", "61121131", "11112136", "31112134", "51112132",
        "11121226", "31121224", "51121222", "21112225", "41112223", "61112221",
        "21121315", "41121313", "61121311", "11112316",
        /* Column 20 */
        "31112314", "51112312", "22121134", "42121132", "11221135", "31221133",
        "51221131", "12112135", "32112133", "52112131", "12121225", "32121223",
        "52121221", "21221224", "41221222", "22112224", "42112222", "11212225",
        "22121314", "42121312", "11221315", "31221313", "51221311", "12112315",
        "32112313", "52112311", "21212314", "41212312", "23121133", "43121131",
        "12221134", "32221132", "21321133", "41321131", "13112134", "33112132",
        "13121224", "33121222", "11312134", "22221223", "42221221", "11321224",
        "31321222", "23112223", "43112221", "12212224", "23121313", "43121311",
        "12221314", "32221312", "21321313", "41321311", "13112314", "33112312",
        "22212313", "42212311", "11312314", "31312312",
        /* Column 21 */
        "24121132", "13221133", "33221131", "22321132", "11421133", "31421131",
        "14112133", "34112131", "14121223", "34121221", "12312133", "23221222",
        "12321223", "32321221", "21421222", "24112222", "13212223", "24121312",
        "13221313", "33221311", "11412223", "22321312", "11421313", "31421311",
        "14112313", "34112311", "23212312", "12312313", "32312311", "21412312",
        "25121131", "14221132", "23321131", "12421132", "21521131", "15112132",
        "15121222", "13312132", "24221221", "13321222", "11512132", "22421221",
        "11521222", "25112221", "14212222", "25121311", "14221312", "12412222",
        "23321311", "12421312", "21521311", "15112312", "24212311", "13312312",
        "22412311", "11512312", "15221131", "13421131",
        /* Column 22 */
        "16112131", "16121221", "14312131", "14321221", "12512131", "12521221",
        "15212221", "15221311", "13412221", "13421311", "11612221", "16112311",
        "14312311", "12512311", "21131134", "41131132", "11122135", "31122133",
        "51122131", "11131225", "31131223", "51131221", "21113134", "41113132",
        "21122224", "41122222", "21131314", "41131312", "11113225", "31113223",
        "51113221", "11122315", "31122313", "51122311", "21113314", "41113312",
        "22131133", "42131131", "11231134", "31231132", "12122134", "32122132",
        "12131224", "32131222", "21231223", "41231221", "22113133", "42113131",
        "11213134", "22122223", "42122221", "11222224", "22131313", "42131311",
        "11231314", "31231312", "12113224", "32113222",
        /* Column 23 */
        "12122314", "32122312", "21222313", "41222311", "22113313", "42113311",
        "11213314", "31213312", "23131132", "12231133", "32231131", "21331132",
        "13122133", "33122131", "13131223", "33131221", "11322133", "22231222",
        "11331223", "31331221", "23113132", "12213133", "23122222", "12222223",
        "23131312", "12231313", "32231311", "21331312", "13113223", "33113221",
        "13122313", "33122311", "11313223", "22222312", "11322313", "31322311",
        "23113312", "12213313", "32213311", "21313312", "24131131", "13231132",
        "22331131", "11431132", "14122132", "14131222", "12322132", "23231221",
        "12331222", "21431221", "24113131", "13213132", "24122221", "13222222",
        "24131311", "11413132", "13231312", "11422222",
        /* Column 24 */
        "22331311", "11431312", "14113222", "14122312", "12313222", "23222311",
        "12322312", "21422311", "24113311", "13213312", "22313311", "11413312",
        "14231131", "12431131", "15122131", "15131221", "13322131", "13331221",
        "11522131", "14213131", "14222221", "12413131", "14231311", "12422221",
        "12431311", "15113221", "15122311", "13313221", "13322311", "11513221",
        "11522311", "14213311", "12413311", "21141133", "41141131", "11132134",
        "31132132", "11141224", "31141222", "21123133", "41123131", "21132223",
        "41132221", "21141313", "41141311", "11114134", "31114132", "11123224",
        "31123222", "11132314", "31132312", "21114223", "41114221", "21123313",
        "41123311", "11114314", "31114312", "22141132",
        /* Column 25 */
        "11241133", "31241131", "12132133", "32132131", "12141223", "32141221",
        "21241222", "22123132", "11223133", "22132222", "11232223", "22141312",
        "11241313", "31241311", "12114133", "32114131", "12123223", "32123221",
        "12132313", "32132311", "21232312", "22114222", "11214223", "22123312",
        "11223313", "31223311", "12114313", "32114311", "21214312", "23141131",
        "12241132", "21341131", "13132132", "13141222", "11332132", "22241221",
        "11341222", "23123131", "12223132", "23132221", "12232222", "23141311",
        "12241312", "21341311", "13114132", "13123222", "11314132", "13132312",
        "11323222", "22232311", "11332312", "23114221", "12214222", "23123311",
        "12223312", "21323311", "13114312", "22214311",
        /* Column 26 */
        "11314312", "13241131", "14132131", "14141221", "12332131", "12341221",
        "13223131", "13232221", "11423131", "13241311", "11432221", "14114131",
        "14123221", "12314131", "14132311", "12323221", "12332311", "13214221",
        "13223311", "11414221", "11423311", "14114311", "12314311", "21151132",
        "11142133", "31142131", "11151223", "31151221", "21133132", "21142222",
        "21151312", "11124133", "31124131", "11133223", "31133221", "11142313",
        "31142311", "21115132", "21124222", "21133312", "11115223", "31115221",
        "11124313", "31124311", "22151131", "11251132", "12142132", "12151222",
        "21251221", "22133131", "11233132", "22142221", "11242222", "22151311",
        "11251312", "12124132", "12133222", "12142312",
        /* Column 27 */
        "21242311", "22115131", "11215132", "22124221", "11224222", "22133311",
        "11233312", "12115222", "12124312", "21224311", "12251131", "13142131",
        "13151221", "11342131", "12233131", "12242221", "12251311", "13124131",
        "13133221", "11324131", "13142311", "11333221", "11342311", "12215131",
        "12224221", "12233311", "13115221", "13124311", "11315221", "11324311",
        "21161131", "11152132", "11161222", "21143131", "21152221", "21161311",
        "11134132", "11143222", "11152312", "21125131", "21134221", "21143311",
        "11116132", "11125222", "11134312", "12152131", "12161221", "11243131",
        "11252221", "12134131", "12143221", "12152311", "11225131", "11234221",
        "11243311", "12116131", "12125221", "12134311",
        /* Column 28 */
        "21111235", "41111233", "61111231", "11111326", "31111324", "51111322",
        "21111415", "41111413", "61111411", "21211144", "41211142", "22111234",
        "42111232", "11211235", "31211233", "51211231", "12111325", "32111323",
        "52111321", "21211324", "41211322", "22111414", "42111412", "11211415",
        "31211413", "51211411", "22211143", "42211141", "11311144", "31311142",
        "23111233", "43111231", "12211234", "32211232", "21311233", "41311231",
        "13111324", "33111322", "22211323", "42211321", "11311324", "31311322",
        "23111413", "43111411", "12211414", "32211412", "21311413", "41311411",
        "23211142", "12311143", "32311141", "21411142", "24111232", "13211233",
        "33211231", "22311232", "11411233", "31411231",
        /* Column 29 */
        "14111323", "34111321", "23211322", "12311323", "32311321", "21411322",
        "24111412", "13211413", "33211411", "22311412", "11411413", "31411411",
        "24211141", "13311142", "22411141", "11511142", "25111231", "14211232",
        "23311231", "12411232", "21511231", "15111322", "24211321", "13311322",
        "22411321", "11511322", "25111411", "14211412", "23311411", "12411412",
        "21511411", "14311141", "12511141", "15211231", "13411231", "11611231",
        "16111321", "14311321", "12511321", "15211411", "13411411", "11611411",
        "31121143", "51121141", "21112144", "41112142", "21121234", "41121232",
        "11112235", "31112233", "51112231", "11121325", "31121323", "51121321",
        "21112324", "41112322", "21121414", "41121412",
        /* Column 30 */
        "11112415", "31112413", "51112411", "12121144", "32121142", "21221143",
        "41221141", "22112143", "42112141", "11212144", "22121233", "42121231",
        "11221234", "31221232", "12112234", "32112232", "12121324", "32121322",
        "21221323", "41221321", "22112323", "42112321", "11212324", "22121413",
        "42121411", "11221414", "31221412", "12112414", "32112412", "21212413",
        "41212411", "13121143", "33121141", "22221142", "11321143", "31321141",
        "23112142", "12212143", "23121232", "12221233", "32221231", "21321232",
        "13112233", "33112231", "13121323", "33121321", "11312233", "22221322",
        "11321323", "31321321", "23112322", "12212323", "23121412", "12221413",
        "32221411", "21321412", "13112413", "33112411",
        /* Column 31 */
        "22212412", "11312413", "31312411", "14121142", "23221141", "12321142",
        "21421141", "24112141", "13212142", "24121231", "13221232", "11412142",
        "22321231", "11421232", "14112232", "14121322", "12312232", "23221321",
        "12321322", "21421321", "24112321", "13212322", "24121411", "13221412",
        "11412322", "22321411", "11421412", "14112412", "23212411", "12312412",
        "21412411", "15121141", "13321141", "11521141", "14212141", "14221231",
        "12412141", "12421231", "15112231", "15121321", "13312231", "13321321",
        "11512231", "11521321", "14212321", "14221411", "12412321", "12421411",
        "15112411", "13312411", "11512411", "11131144", "31131142", "21122143",
        "41122141", "21131233", "41131231", "11113144",
        /* Column 32 */
        "31113142", "11122234", "31122232", "11131324", "31131322", "21113233",
        "41113231", "21122323", "41122321", "21131413", "41131411", "11113324",
        "31113322", "11122414", "31122412", "21113413", "41113411", "12131143",
        "32131141", "21231142", "22122142", "11222143", "22131232", "11231233",
        "31231231", "12113143", "32113141", "12122233", "32122231", "12131323",
        "32131321", "21231322", "22113232", "11213233", "22122322", "11222323",
        "22131412", "11231413", "31231411", "12113323", "32113321", "12122413",
        "32122411", "21222412", "22113412", "11213413", "31213411", "13131142",
        "22231141", "11331142", "23122141", "12222142", "23131231", "12231232",
        "21331231", "13113142", "13122232", "11313142",
        /* Column 33 */
        "13131322", "11322232", "22231321", "11331322", "23113231", "12213232",
        "23122321", "12222322", "23131411", "12231412", "21331411", "13113322",
        "13122412", "11313322", "22222411", "11322412", "23113411", "12213412",
        "21313411", "14131141", "12331141", "13222141", "13231231", "11422141",
        "11431231", "14113141", "14122231", "12313141", "14131321", "12322231",
        "12331321", "13213231", "13222321", "11413231", "13231411", "11422321",
        "11431411", "14113321", "14122411", "12313321", "12322411", "13213411",
        "11413411", "11141143", "31141141", "21132142", "21141232", "11123143",
        "31123141", "11132233", "31132231", "11141323", "31141321", "21114142",
        "21123232", "21132322", "21141412", "11114233",
        /* Column 34 */
        "31114231", "11123323", "31123321", "11132413", "31132411", "21114322",
        "21123412", "12141142", "21241141", "22132141", "11232142", "22141231",
        "11241232", "12123142", "12132232", "12141322", "21241321", "22114141",
        "11214142", "22123231", "11223232", "22132321", "11232322", "22141411",
        "11241412", "12114232", "12123322", "12132412", "21232411", "22114321",
        "11214322", "22123411", "11223412", "13141141", "11341141", "12232141",
        "12241231", "13123141", "13132231", "11323141", "13141321", "11332231",
        "11341321", "12214141", "12223231", "12232321", "12241411", "13114231",
        "13123321", "11314231", "13132411", "11323321", "11332411", "12214321",
        "12223411", "11151142", "21142141", "21151231",
        /* Column 35 */
        "11133142", "11142232", "11151322", "21124141", "21133231", "21142321",
        "21151411", "11115142", "11124232", "11133322", "11142412", "21115231",
        "21124321", "21133411", "12151141", "11242141", "11251231", "12133141",
        "12142231", "12151321", "11224141", "11233231", "11242321", "11251411",
        "12115141", "12124231", "12133321", "12142411", "11215231", "11224321",
        "11233411", "11161141", "11143141", "11152231", "11161321", "11125141",
        "11134231", "11143321", "11152411", "11111245", "31111243", "51111241",
        "21111334", "41111332", "11111425", "31111423", "51111421", "21111514",
        "41111512", "31211152", "12111244", "32111242", "21211243", "41211241",
        "22111333", "42111331", "11211334", "31211332",
        /* Column 36 */
        "12111424", "32111422", "21211423", "41211421", "22111513", "42111511",
        "11211514", "31211512", "12211153", "32211151", "21311152", "13111243",
        "33111241", "22211242", "11311243", "31311241", "23111332", "12211333",
        "32211331", "21311332", "13111423", "33111421", "22211422", "11311423",
        "31311421", "23111512", "12211513", "32211511", "21311512", "13211152",
        "22311151", "11411152", "14111242", "23211241", "12311242", "21411241",
        "24111331", "13211332", "22311331", "11411332", "14111422", "23211421",
        "12311422", "21411421", "24111511", "13211512", "22311511", "11411512",
        "14211151", "12411151", "15111241", "13311241", "11511241", "14211331",
        "12411331", "15111421", "13311421", "11511421",
        /* Column 37 */
        "14211511", "12411511", "21121153", "41121151", "11112154", "31112152",
        "11121244", "31121242", "21112243", "41112241", "21121333", "41121331",
        "11112334", "31112332", "11121424", "31121422", "21112423", "41112421",
        "21121513", "41121511", "11112514", "31112512", "22121152", "11221153",
        "31221151", "12112153", "32112151", "12121243", "32121241", "21221242",
        "22112242", "11212243", "22121332", "11221333", "31221331", "12112333",
        "32112331", "12121423", "32121421", "21221422", "22112422", "11212423",
        "22121512", "11221513", "31221511", "12112513", "32112511", "21212512",
        "23121151", "12221152", "21321151", "13112152", "13121242", "11312152",
        "22221241", "11321242", "23112241", "12212242",
        /* Column 38 */
        "23121331", "12221332", "21321331", "13112332", "13121422", "11312332",
        "22221421", "11321422", "23112421", "12212422", "23121511", "12221512",
        "21321511", "13112512", "22212511", "11312512", "13221151", "11421151",
        "14112151", "14121241", "12312151", "12321241", "13212241", "13221331",
        "11412241", "11421331", "14112331", "14121421", "12312331", "12321421",
        "13212421", "13221511", "11412421", "11421511", "14112511", "12312511",
        "21131152", "11122153", "31122151", "11131243", "31131241", "21113152",
        "21122242", "21131332", "11113243", "31113241", "11122333", "31122331",
        "11131423", "31131421", "21113332", "21122422", "21131512", "11113423",
        "31113421", "11122513", "31122511", "22131151",
        /* Column 39 */
        "11231152", "12122152", "12131242", "21231241", "22113151", "11213152",
        "22122241", "11222242", "22131331", "11231332", "12113242", "12122332",
        "12131422", "21231421", "22113331", "11213332", "22122421", "11222422",
        "22131511", "11231512", "12113422", "12122512", "21222511", "12231151",
        "13122151", "13131241", "11322151", "11331241", "12213151", "12222241",
        "12231331", "13113241", "13122331", "11313241", "13131421", "11322331",
        "11331421", "12213331", "12222421", "12231511", "13113421", "13122511",
        "11313421", "11322511", "21141151", "11132152", "11141242", "21123151",
        "21132241", "21141331", "11114152", "11123242", "11132332", "11141422",
        "21114241", "21123331", "21132421", "21141511",
        /* Column 40 */
        "11114332", "11123422", "11132512", "11241151", "12132151", "12141241",
        "11223151", "11232241", "11241331", "12114151", "12123241", "12132331",
        "12141421", "11214241", "11223331", "11232421", "11241511", "12114331",
        "12123421", "12132511", "11142151", "11151241", "11124151", "11133241",
        "11142331", "11151421", "11115241", "11124331", "11133421", "11142511",
        "21111253", "41111251", "11111344", "31111342", "21111433", "41111431",
        "11111524", "31111522", "21111613", "41111611", "21211162", "22111252",
        "11211253", "31211251", "12111343", "32111341", "21211342", "22111432",
        "11211433", "31211431", "12111523", "32111521", "21211522", "22111612",
        "11211613", "31211611", "22211161", "11311162",
        /* Column 41 */
        "23111251", "12211252", "21311251", "13111342", "22211341", "11311342",
        "23111431", "12211432", "21311431", "13111522", "22211521", "11311522",
        "23111611", "12211612", "21311611", "12311161", "13211251", "11411251",
        "14111341", "12311341", "13211431", "11411431", "14111521", "12311521",
        "13211611", "11411611", "31121161", "21112162", "21121252", "11112253",
        "31112251", "11121343", "31121341", "21112342", "21121432", "11112433",
        "31112431", "11121523", "31121521", "21112522", "21121612",
        /* Column 42 */
        "12121162", "21221161", "22112161", "11212162", "22121251", "11221252",
        "12112252", "12121342", "21221341", "22112341", "11212342", "22121431",
        "11221432", "12112432", "12121522", "21221521", "22112521", "11212522",
        "22121611", "11221612", "13121161", "11321161", "12212161", "12221251",
        "13112251", "13121341", "11312251", "11321341", "12212341", "12221431",
        "13112431", "13121521", "11312431", "11321521", "12212521", "12221611",
        "11131162", "21122161", "21131251", "11113162"
    };

    private static final char[] C49_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', '-', '.', ' ', '$', '/', '+',
        '%', '!', '&', '*'
    };

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {
        int codeword_count = 0, pad_count = 0;
        int x_count, y_count, z_count, posn_val, local_value;
        int[] codewords = new int[170];
        int[][] c_grid = new int[8][8];
        int[][] w_grid = new int[8][4];

        if (!content.matches("[\u0000-\u007F]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        inputData = toBytes(content, StandardCharsets.US_ASCII);

        StringBuilder intermediate = new StringBuilder(inputData.length + 8);
        if (inputDataType == DataType.GS1) {
            intermediate.append('*'); // FNC1
        }
        for (int i = 0; i < inputData.length; i++) {
            int c = inputData[i];
            if (c == FNC1) {
                intermediate.append('*'); // FNC1
            } else {
                intermediate.append(C49_TABLE7[c]);
            }
        }

        int h = intermediate.length();
        int i = 0;

        do {
            if (intermediate.charAt(i) >= '0' && intermediate.charAt(i) <= '9') {

                /* Numeric data */
                int latch = 0;
                int j = 0;
                do {
                    if ((i + j) >= h) {
                        latch = 1;
                    } else {
                        if (intermediate.charAt(i + j) >= '0' && intermediate.charAt(i + j) <= '9') {
                            j++;
                        } else {
                            latch = 1;
                        }
                    }
                } while (latch == 0);

                if (j >= 5) {

                    /* Use Numeric Encodation Method */
                    int block_count, c;
                    int block_remain;
                    int block_value;

                    codewords[codeword_count] = 48; /* Numeric Shift */
                    codeword_count++;

                    block_count = j / 5;
                    block_remain = j % 5;

                    for (c = 0; c < block_count; c++) {
                        if ((c == block_count - 1) && (block_remain == 2)) {
                            /* Rule (d) */
                            block_value = 100000;
                            block_value += (intermediate.charAt(i) - '0') * 1000;
                            block_value += (intermediate.charAt(i + 1) - '0') * 100;
                            block_value += (intermediate.charAt(i + 2) - '0') * 10;
                            block_value += intermediate.charAt(i + 3) - '0';

                            codewords[codeword_count] = block_value / (48 * 48);
                            block_value = block_value - (48 * 48) * codewords[codeword_count];
                            codeword_count++;
                            codewords[codeword_count] = block_value / 48;
                            block_value = block_value - 48 * codewords[codeword_count];
                            codeword_count++;
                            codewords[codeword_count] = block_value;
                            codeword_count++;
                            i += 4;
                            block_value = (intermediate.charAt(i) - '0') * 100;
                            block_value += (intermediate.charAt(i + 1) - '0') * 10;
                            block_value += intermediate.charAt(i + 2) - '0';

                            codewords[codeword_count] = block_value / 48;
                            block_value = block_value - 48 * codewords[codeword_count];
                            codeword_count++;
                            codewords[codeword_count] = block_value;
                            codeword_count++;
                            i += 3;
                        } else {
                            block_value = (intermediate.charAt(i) - '0') * 10000;
                            block_value += (intermediate.charAt(i + 1) - '0') * 1000;
                            block_value += (intermediate.charAt(i + 2) - '0') * 100;
                            block_value += (intermediate.charAt(i + 3) - '0') * 10;
                            block_value += intermediate.charAt(i + 4) - '0';

                            codewords[codeword_count] = block_value / (48 * 48);
                            block_value = block_value - (48 * 48) * codewords[codeword_count];
                            codeword_count++;
                            codewords[codeword_count] = block_value / 48;
                            block_value = block_value - 48 * codewords[codeword_count];
                            codeword_count++;
                            codewords[codeword_count] = block_value;
                            codeword_count++;
                            i += 5;
                        }
                    }

                    switch (block_remain) {
                    case 1:
                        /* Rule (a) */
                        codewords[codeword_count] = positionOf(intermediate.charAt(i), C49_SET);
                        codeword_count++;
                        i++;
                        break;
                    case 3:
                        /* Rule (b) */
                        block_value = (intermediate.charAt(i) - '0') * 100;
                        block_value += (intermediate.charAt(i + 1) - '0') * 10;
                        block_value += intermediate.charAt(i + 2) - '0';

                        codewords[codeword_count] = block_value / 48;
                        block_value = block_value - 48 * codewords[codeword_count];
                        codeword_count++;
                        codewords[codeword_count] = block_value;
                        codeword_count++;
                        i += 3;
                        break;
                    case 4:
                        /* Rule (c) */
                        block_value = 100000;
                        block_value += (intermediate.charAt(i) - '0') * 1000;
                        block_value += (intermediate.charAt(i + 1) - '0') * 100;
                        block_value += (intermediate.charAt(i + 2) - '0') * 10;
                        block_value += intermediate.charAt(i + 3) - '0';

                        codewords[codeword_count] = block_value / (48 * 48);
                        block_value = block_value - (48 * 48) * codewords[codeword_count];
                        codeword_count++;
                        codewords[codeword_count] = block_value / 48;
                        block_value = block_value - 48 * codewords[codeword_count];
                        codeword_count++;
                        codewords[codeword_count] = block_value;
                        codeword_count++;
                        i += 4;
                        break;
                    }
                    if (i < h) {
                        /* There is more to add */
                        codewords[codeword_count] = 48; /* Numeric Shift */
                        codeword_count++;
                    }
                } else {
                    codewords[codeword_count] = positionOf(intermediate.charAt(i), C49_SET);
                    codeword_count++;
                    i++;
                }
            } else {
                codewords[codeword_count] = positionOf(intermediate.charAt(i), C49_SET);
                codeword_count++;
                i++;
            }
        } while (i < h);

        int M;
        switch (codewords[0]) { /* Set starting mode value */
            case 48:
                M = 2;
                break;
            case 43:
                M = 4;
                break;
            case 44:
                M = 5;
                break;
            default:
                M = 0;
                break;
        }

        if (M != 0) {
            for (i = 0; i < codeword_count; i++) {
                codewords[i] = codewords[i + 1];
            }
            codeword_count--;
        }

        if (codeword_count > 49) {
            throw new OkapiException("Input too long");
        }

        infoLine("Starting Mode (M): " + M);

        /* Place codewords in code character array (c grid) */
        int rows = 0;
        do {
            for (i = 0; i < 7; i++) {
                if (((rows * 7) + i) < codeword_count) {
                    c_grid[rows][i] = codewords[(rows * 7) + i];
                } else {
                    c_grid[rows][i] = 48; /* Pad */
                    pad_count++;
                }
            }
            rows++;
        } while ((rows * 7) < codeword_count);

        if ((((rows <= 6) && (pad_count < 5))) || (rows > 6) || (rows == 1)) {
            /* Add a row */
            for (i = 0; i < 7; i++) {
                c_grid[rows][i] = 48; /* Pad */
            }
            rows++;
        }

        /* Add row count and mode character */
        c_grid[rows - 1][6] = (7 * (rows - 2)) + M;

        /* Add row check character */
        for (i = 0; i < rows - 1; i++) {
            int row_sum = 0;
            for (int j = 0; j < 7; j++) {
                row_sum += c_grid[i][j];
            }
            c_grid[i][7] = row_sum % 49;
        }

        /* Calculate Symbol Check Characters */
        posn_val = 0;
        x_count = c_grid[rows - 1][6] * 20;
        y_count = c_grid[rows - 1][6] * 16;
        z_count = c_grid[rows - 1][6] * 38;
        for (i = 0; i < rows - 1; i++) {
            for (int j = 0; j < 4; j++) {
                local_value = (c_grid[i][2 * j] * 49) + c_grid[i][(2 * j) + 1];
                x_count += C49_X_WEIGHT[posn_val] * local_value;
                y_count += C49_Y_WEIGHT[posn_val] * local_value;
                z_count += C49_Z_WEIGHT[posn_val] * local_value;
                posn_val++;
            }
        }

        if (rows > 6) {
            /* Add Z Symbol Check */
            c_grid[rows - 1][0] = (z_count % 2401) / 49;
            c_grid[rows - 1][1] = (z_count % 2401) % 49;
        }

        local_value = (c_grid[rows - 1][0] * 49) + c_grid[rows - 1][1];
        x_count += C49_X_WEIGHT[posn_val] * local_value;
        y_count += C49_Y_WEIGHT[posn_val] * local_value;
        posn_val++;

        /* Add Y Symbol Check */
        c_grid[rows - 1][2] = (y_count % 2401) / 49;
        c_grid[rows - 1][3] = (y_count % 2401) % 49;

        local_value = (c_grid[rows - 1][2] * 49) + c_grid[rows - 1][3];
        x_count += C49_X_WEIGHT[posn_val] * local_value;

        /* Add X Symbol Check */
        c_grid[rows - 1][4] = (x_count % 2401) / 49;
        c_grid[rows - 1][5] = (x_count % 2401) % 49;

        infoLine("Check Characters: " + (z_count % 2401) + " " + (y_count % 2401));

        /* Add last row check character */
        int sum = 0;
        for (i = 0; i < 7; i++) {
            sum += c_grid[rows - 1][i];
        }
        c_grid[rows - 1][7] = sum % 49;

        info("Codewords: ");
        /* Transfer data to symbol character array (w grid) */
        for (i = 0; i < rows; i++) {
            for (int j = 0; j < 4; j++) {
                w_grid[i][j] = (c_grid[i][2 * j] * 49) + c_grid[i][(2 * j) + 1];
                infoSpace(c_grid[i][2 * j]);
                infoSpace(c_grid[i][(2 * j) + 1]);
            }
        }
        infoLine();

        readable = "";
        pattern = new String[rows];
        row_count = rows;
        row_height = new int[rows];

        info("Symbol Characters: ");
        for (i = 0; i < rows; i++) {
            StringBuilder rowPattern = new StringBuilder(3 + (4 * 8));
            rowPattern.append("11"); /* Start character */
            for (int j = 0; j < 4; j++) {
                infoSpace(w_grid[i][j]);
                if (i != (rows - 1)) {
                    if (C49_TABLE4[i].charAt(j) == 'E') {
                        /* Even Parity */
                        rowPattern.append(C49_APPXE_EVEN[w_grid[i][j]]);
                    } else {
                        /* Odd Parity */
                        rowPattern.append(C49_APPXE_ODD[w_grid[i][j]]);
                    }
                } else {
                    /* Last row uses all even parity */
                    rowPattern.append(C49_APPXE_EVEN[w_grid[i][j]]);
                }
            }
            rowPattern.append('4'); /* Stop character */
            pattern[i] = rowPattern.toString();
            row_height[i] = 10;
        }
        infoLine();
    }

    @Override
    protected void plotSymbol() {

        int xBlock, yBlock;
        int x, y, w, h;
        boolean black;

        rectangles.clear();
        y = 1;
        h = 1;
        for (yBlock = 0; yBlock < row_count; yBlock++) {
            black = true;
            x = 15;
            for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) {
                if (black) {
                    black = false;
                    w = pattern[yBlock].charAt(xBlock) - '0';
                    if (row_height[yBlock] == -1) {
                        h = default_height;
                    } else {
                        h = row_height[yBlock];
                    }
                    if (w != 0 && h != 0) {
                        Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
                        rectangles.add(rect);
                    }
                    if (x + w > symbol_width) {
                        symbol_width = x + w;
                    }
                } else {
                    black = true;
                }
                x += pattern[yBlock].charAt(xBlock) - '0';
            }
            y += h;
            if (y > symbol_height) {
                symbol_height = y;
            }
            /* Add bars between rows */
            if (yBlock != row_count - 1) {
                Rectangle2D.Double rect = new Rectangle2D.Double(15, y - 1, symbol_width - 15, 2);
                rectangles.add(rect);
            }
        }

        /* Add top and bottom binding bars */
        Rectangle2D.Double top = new Rectangle2D.Double(0, 0, symbol_width + 15, 2);
        rectangles.add(top);
        Rectangle2D.Double bottom = new Rectangle2D.Double(0, y - 1, symbol_width + 15, 2);
        rectangles.add(bottom);
        symbol_width += 15;
        symbol_height += 1;
    }
}
 public class Code93 extends Symbol {

    /**
     * Code 93 control characters, indexed by ASCII codes (NOTE: a = Ctrl $,
     * b = Ctrl %, c = Ctrl /, d = Ctrl + for sequences of two characters).
     */
    private static final String[] CODE_93_CTRL = {
        "bU", "aA", "aB", "aC", "aD", "aE", "aF", "aG", "aH", "aI",
        "aJ", "aK", "aL", "aM", "aN", "aO", "aP", "aQ", "aR", "aS",
        "aT", "aU", "aV", "aW", "aX", "aY", "aZ", "bA", "bB", "bC",
        "bD", "bE", " ",  "cA", "cB", "cC", "$",  "%",  "cF", "cG",
        "cH", "cI", "cJ", "+",  "cL", "-",  ".",  "/",  "0",  "1",
        "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",  "cZ", "bF",
        "bG", "bH", "bI", "bJ", "bV", "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",  "bK", "bL", "bM", "bN", "bO", "bW", "dA", "dB", "dC",
        "dD", "dE", "dF", "dG", "dH", "dI", "dJ", "dK", "dL", "dM",
        "dN", "dO", "dP", "dQ", "dR", "dS", "dT", "dU", "dV", "dW",
        "dX", "dY", "dZ", "bP", "bQ", "bR", "bS", "bT" };

    /**
     * Mapping of control characters to pattern table index (NOTE: a = Ctrl $,
     * b = Ctrl %, c = Ctrl /, d = Ctrl + for sequences of two characters).
     */
    private static final char[] CODE_93_LOOKUP = {
        '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' };

    /** Code 93 pattern table. */
    private static final String[] CODE_93_TABLE = {
        "131112", "111213", "111312", "111411", "121113",
        "121212", "121311", "111114", "131211", "141111",
        "211113", "211212", "211311", "221112", "221211",
        "231111", "112113", "112212", "112311", "122112",
        "132111", "111123", "111222", "111321", "121122",
        "131121", "212112", "212211", "211122", "211221",
        "221121", "222111", "112122", "112221", "122121",
        "123111", "121131", "311112", "311211", "321111",
        "112131", "113121", "211131", "121221", "312111",
        "311121", "122211" };

    /** Whether or not to show check digits in the human-readable text. */
    private boolean showCheckDigits = true;

    /** Optional start/stop delimiter to be shown in the human-readable text. */
    private Character startStopDelimiter;

    /**
     * Sets whether or not to show check digits in the human-readable text (defaults to <code>true</code>).
     *
     * @param showCheckDigits whether or not to show check digits in the human-readable text
     */
    public void setShowCheckDigits(boolean showCheckDigits) {
        this.showCheckDigits = showCheckDigits;
    }

    /**
     * Returns whether or not this symbol shows check digits in the human-readable text.
     *
     * @return whether or not this symbol shows check digits in the human-readable text
     */
    public boolean getShowCheckDigits() {
        return showCheckDigits;
    }

    /**
     * Sets an optional start/stop delimiter to be shown in the human-readable text (defaults to <code>null</code>).
     *
     * @param startStopDelimiter an optional start/stop delimiter to be shown in the human-readable text
     */
    public void setStartStopDelimiter(Character startStopDelimiter) {
        this.startStopDelimiter = startStopDelimiter;
    }

    /**
     * Returns the optional start/stop delimiter to be shown in the human-readable text.
     *
     * @return the optional start/stop delimiter to be shown in the human-readable text
     */
    public Character getStartStopDelimiter() {
        return startStopDelimiter;
    }

    /** {@inheritDoc} */
    @Override
    protected void encode() {

        char[] controlChars = toControlChars(content);
        int l = controlChars.length;

        if (!content.matches("[\u0000-\u007F]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        int[] values = new int[controlChars.length + 2];
        for (int i = 0; i < l; i++) {
            values[i] = positionOf(controlChars[i], CODE_93_LOOKUP);
        }

        int c = calculateCheckDigitC(values, l);
        values[l] = c;
        l++;

        int k = calculateCheckDigitK(values, l);
        values[l] = k;
        l++;

        readable = content;
        if (showCheckDigits) {
            readable = readable + CODE_93_LOOKUP[c] + CODE_93_LOOKUP[k];
        }
        if (startStopDelimiter != null) {
            readable = startStopDelimiter + readable + startStopDelimiter;
        }

        infoLine("Check Digit C: " + c);
        infoLine("Check Digit K: " + k);
        pattern = new String[] { toPattern(values) };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private static char[] toControlChars(String s) {
        StringBuilder buffer = new StringBuilder();
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            int asciiCode = chars[i];
            buffer.append(CODE_93_CTRL[asciiCode]);
        }
        return buffer.toString().toCharArray();
    }

    private static int calculateCheckDigitC(int[] values, int length) {
        int c = 0;
        int weight = 1;
        for (int i = length - 1; i >= 0; i--) {
            c += values[i] * weight;
            weight++;
            if (weight == 21) {
                weight = 1;
            }
        }
        c = c % 47;
        return c;
    }

    private static int calculateCheckDigitK(int[] values, int length) {
        int k = 0;
        int weight = 1;
        for (int i = length - 1; i >= 0; i--) {
            k += values[i] * weight;
            weight++;
            if (weight == 16) {
                weight = 1;
            }
        }
        k = k % 47;
        return k;
    }

    private static String toPattern(int[] values) {
        StringBuilder buffer = new StringBuilder("111141");
        for (int i = 0; i < values.length; i++) {
            buffer.append(CODE_93_TABLE[values[i]]);
        }
        buffer.append("1111411");
        return buffer.toString();
    }

    /** {@inheritDoc} */
    @Override
    protected int[] getCodewords() {
        return getPatternAsCodewords(6);
    }
}
 public class CodeOne extends Symbol {

    public enum Version {
        NONE, A, B, C, D, E, F, G, H, S, T
    }

    private static final int[] C40_SHIFT = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3
    };

    private static final int[] C40_VALUE = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6,
        7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16,
        17, 18, 19, 20, 21, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
        27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 22, 23, 24, 25, 26,
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
    };

    private static final int[] TEXT_SHIFT = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2,
        3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 3, 3, 3, 3, 3
    };

    private static final int[] TEXT_VALUE = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6,
        7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16,
        17, 18, 19, 20, 21, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 22, 23, 24, 25, 26, 0, 14,
        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
        33, 34, 35, 36, 37, 38, 39, 27, 28, 29, 30, 31
    };

    private static final int[] C1_HEIGHT = {
        16, 22, 28, 40, 52, 70, 104, 148
    };
    private static final int[] C1_WIDTH = {
        18, 22, 32, 42, 54, 76, 98, 134
    };
    private static final int[] C1_DATA_LENGTH = {
        10, 19, 44, 91, 182, 370, 732, 1480
    };
    private static final int[] C1_ECC_LENGTH = {
        10, 16, 26, 44, 70, 140, 280, 560
    };
    private static final int[] C1_BLOCKS = {
        1, 1, 1, 1, 1, 2, 4, 8
    };
    private static final int[] C1_DATA_BLOCKS = {
        10, 19, 44, 91, 182, 185, 183, 185
    };
    private static final int[] C1_ECC_BLOCKS = {
        10, 16, 26, 44, 70, 70, 70, 70
    };
    private static final int[] C1_GRID_WIDTH = {
        4, 5, 7, 9, 12, 17, 22, 30
    };
    private static final int[] C1_GRID_HEIGHT = {
        5, 7, 10, 15, 21, 30, 46, 68
    };

    private enum Mode {
        C1_ASCII, C1_C40, C1_DECIMAL, C1_TEXT, C1_EDI, C1_BYTE
    }

    private Version preferredVersion = Version.NONE;

    private int[] data = new int[1500];
    private int[][] datagrid = new int[136][120];
    private boolean[][] outputGrid = new boolean[148][134];

    /**
     * Sets the preferred symbol size / version. Versions A to H are square symbols.
     * Version S and T are fixed height symbols. This value may be ignored if the input
     * data does not fit in the specified version.
     *
     * @param version the preferred symbol version
     */
    public void setPreferredVersion(Version version) {
        preferredVersion = version;
    }

    /**
     * Returns the preferred symbol version.
     *
     * @return the preferred symbol version
     */
    public Version getPreferredVersion() {
        return preferredVersion;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {

        int size = 1, i, j, data_blocks;
        int row, col;
        int sub_version = 0;
        int codewords;
        int[] ecc = new int[600];
        int[] stream = new int[2100];
        int block_width;
        int length = content.length();
        ReedSolomon rs = new ReedSolomon();
        int data_length;
        int data_cw, ecc_cw;
        int[] sub_data = new int[190];
        StringBuilder bin = new StringBuilder();

        if (!content.matches("[\u0000-\u00FF]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        if (preferredVersion == Version.S) {
            /* Version S */

            infoLine("Version: S");

            if (length > 18) {
                throw new OkapiException("Input data too long");
            }

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

            sub_version = 3;
            codewords = 12;
            block_width = 6; /* Version S-30 */
            if (length <= 12) {
                sub_version = 2;
                codewords = 8;
                block_width = 4;
            } /* Version S-20 */
            if (length <= 6) {
                sub_version = 1;
                codewords = 4;
                block_width = 2;
            } /* Version S-10 */

            BigInteger elreg = new BigInteger(content);

            for (i = 0; i < codewords; i++) {
                BigInteger codewordValue = elreg.shiftRight(5 * i);
                codewordValue = codewordValue.and(BigInteger.valueOf(0b11111));
                data[codewords - i - 1] = codewordValue.intValue();
            }

            logCodewords(codewords);

            rs.init_gf(0x25);
            rs.init_code(codewords, 1);
            rs.encode(codewords, data);

            infoLine("ECC Codeword Count: " + codewords);

            for (i = 0; i < codewords; i++) {
                stream[i] = data[i];
                stream[i + codewords] = rs.getResult(codewords - i - 1);
            }

            for (i = 0; i < 136; i++) {
                for (j = 0; j < 120; j++) {
                    datagrid[i][j] = '0';
                }
            }

            i = 0;
            for (row = 0; row < 2; row++) {
                for (col = 0; col < block_width; col++) {
                    if ((stream[i] & 0x10) != 0) {
                        datagrid[row * 2][col * 5] = '1';
                    }
                    if ((stream[i] & 0x08) != 0) {
                        datagrid[row * 2][(col * 5) + 1] = '1';
                    }
                    if ((stream[i] & 0x04) != 0) {
                        datagrid[row * 2][(col * 5) + 2] = '1';
                    }
                    if ((stream[i] & 0x02) != 0) {
                        datagrid[(row * 2) + 1][col * 5] = '1';
                    }
                    if ((stream[i] & 0x01) != 0) {
                        datagrid[(row * 2) + 1][(col * 5) + 1] = '1';
                    }
                    if ((stream[i + 1] & 0x10) != 0) {
                        datagrid[row * 2][(col * 5) + 3] = '1';
                    }
                    if ((stream[i + 1] & 0x08) != 0) {
                        datagrid[row * 2][(col * 5) + 4] = '1';
                    }
                    if ((stream[i + 1] & 0x04) != 0) {
                        datagrid[(row * 2) + 1][(col * 5) + 2] = '1';
                    }
                    if ((stream[i + 1] & 0x02) != 0) {
                        datagrid[(row * 2) + 1][(col * 5) + 3] = '1';
                    }
                    if ((stream[i + 1] & 0x01) != 0) {
                        datagrid[(row * 2) + 1][(col * 5) + 4] = '1';
                    }
                    i += 2;
                }
            }

            infoLine("Grid Size: " + block_width + " X " + 2);

            size = 9;
            row_count = 8;
            symbol_width = 10 * sub_version + 1;
        }

        if (preferredVersion == Version.T) {
            /* Version T */

            infoLine("Version: T");

            for (i = 0; i < 40; i++) {
                data[i] = 0;
            }
            data_length = encodeAsCode1Data();

            if (data_length > 38) {
                throw new OkapiException("Input data too long");
            }

            size = 10;
            sub_version = 3;
            data_cw = 38;
            ecc_cw = 22;
            block_width = 12;
            if (data_length <= 24) {
                sub_version = 2;
                data_cw = 24;
                ecc_cw = 16;
                block_width = 8;
            }
            if (data_length <= 10) {
                sub_version = 1;
                data_cw = 10;
                ecc_cw = 10;
                block_width = 4;
            }

            logCodewords(data_length);

            for (i = data_length; i < data_cw; i++) {
                data[i] = 129; /* Pad */
            }

            /* Calculate error correction data */
            rs.init_gf(0x12d);
            rs.init_code(ecc_cw, 1);
            rs.encode(data_cw, data);

            infoLine("ECC Codeword Count: " + ecc_cw);

            /* "Stream" combines data and error correction data */
            for (i = 0; i < data_cw; i++) {
                stream[i] = data[i];
            }
            for (i = 0; i < ecc_cw; i++) {
                stream[data_cw + i] = rs.getResult(ecc_cw - i - 1);
            }

            for (i = 0; i < 136; i++) {
                for (j = 0; j < 120; j++) {
                    datagrid[i][j] = '0';
                }
            }

            i = 0;
            for (row = 0; row < 5; row++) {
                for (col = 0; col < block_width; col++) {
                    if ((stream[i] & 0x80) != 0) {
                        datagrid[row * 2][col * 4] = '1';
                    }
                    if ((stream[i] & 0x40) != 0) {
                        datagrid[row * 2][(col * 4) + 1] = '1';
                    }
                    if ((stream[i] & 0x20) != 0) {
                        datagrid[row * 2][(col * 4) + 2] = '1';
                    }
                    if ((stream[i] & 0x10) != 0) {
                        datagrid[row * 2][(col * 4) + 3] = '1';
                    }
                    if ((stream[i] & 0x08) != 0) {
                        datagrid[(row * 2) + 1][col * 4] = '1';
                    }
                    if ((stream[i] & 0x04) != 0) {
                        datagrid[(row * 2) + 1][(col * 4) + 1] = '1';
                    }
                    if ((stream[i] & 0x02) != 0) {
                        datagrid[(row * 2) + 1][(col * 4) + 2] = '1';
                    }
                    if ((stream[i] & 0x01) != 0) {
                        datagrid[(row * 2) + 1][(col * 4) + 3] = '1';
                    }
                    i++;
                }
            }

            infoLine("Grid Size: " + block_width + " X " + 5);

            row_count = 16;
            symbol_width = (sub_version * 16) + 1;
        }

        if ((preferredVersion != Version.S) && (preferredVersion != Version.T)) {
            /* Version A to H */
            for (i = 0; i < 1500; i++) {
                data[i] = 0;
            }
            data_length = encodeAsCode1Data();

            for (i = 7; i >= 0; i--) {
                if (C1_DATA_LENGTH[i] >= data_length) {
                    size = i + 1;
                }
            }

            if (getSize(preferredVersion) > size) {
                size = getSize(preferredVersion);
            }

            char version = (char) ((size - 1) + 'A');
            infoLine("Version: " + version);
            logCodewords(data_length);

            for (i = data_length; i < C1_DATA_LENGTH[size - 1]; i++) {
                data[i] = 129; /* Pad */
            }

            /* Calculate error correction data */
            data_length = C1_DATA_LENGTH[size - 1];

            data_blocks = C1_BLOCKS[size - 1];

            rs.init_gf(0x12d);
            rs.init_code(C1_ECC_BLOCKS[size - 1], 0);
            for (i = 0; i < data_blocks; i++) {
                for (j = 0; j < C1_DATA_BLOCKS[size - 1]; j++) {
                    sub_data[j] = data[j * data_blocks + i];
                }
                rs.encode(C1_DATA_BLOCKS[size - 1], sub_data);
                for (j = 0; j < C1_ECC_BLOCKS[size - 1]; j++) {
                    ecc[C1_ECC_LENGTH[size - 1] - (j * data_blocks + i) - 1] = rs.getResult(j);
                }
            }

            infoLine("ECC Codeword Count: " + C1_ECC_LENGTH[size - 1]);

            /* "Stream" combines data and error correction data */
            for (i = 0; i < data_length; i++) {
                stream[i] = data[i];
            }
            for (i = 0; i < C1_ECC_LENGTH[size - 1]; i++) {
                stream[data_length + i] = ecc[i];
            }

            for (i = 0; i < 136; i++) {
                for (j = 0; j < 120; j++) {
                    datagrid[i][j] = '0';
                }
            }

            i = 0;
            for (row = 0; row < C1_GRID_HEIGHT[size - 1]; row++) {
                for (col = 0; col < C1_GRID_WIDTH[size - 1]; col++) {
                    if ((stream[i] & 0x80) != 0) {
                        datagrid[row * 2][col * 4] = '1';
                    }
                    if ((stream[i] & 0x40) != 0) {
                        datagrid[row * 2][(col * 4) + 1] = '1';
                    }
                    if ((stream[i] & 0x20) != 0) {
                        datagrid[row * 2][(col * 4) + 2] = '1';
                    }
                    if ((stream[i] & 0x10) != 0) {
                        datagrid[row * 2][(col * 4) + 3] = '1';
                    }
                    if ((stream[i] & 0x08) != 0) {
                        datagrid[(row * 2) + 1][col * 4] = '1';
                    }
                    if ((stream[i] & 0x04) != 0) {
                        datagrid[(row * 2) + 1][(col * 4) + 1] = '1';
                    }
                    if ((stream[i] & 0x02) != 0) {
                        datagrid[(row * 2) + 1][(col * 4) + 2] = '1';
                    }
                    if ((stream[i] & 0x01) != 0) {
                        datagrid[(row * 2) + 1][(col * 4) + 3] = '1';
                    }
                    i++;
                }
            }

            infoLine("Grid Size: " + C1_GRID_WIDTH[size - 1] + " X " + C1_GRID_HEIGHT[size - 1]);

            row_count = C1_HEIGHT[size - 1];
            symbol_width = C1_WIDTH[size - 1];
        }

        for (i = 0; i < 148; i++) {
            for (j = 0; j < 134; j++) {
                outputGrid[i][j] = false;
            }
        }

        switch (size) {
        case 1:
            /* Version A */
            plotCentralFinder(6, 3, 1);
            plotVerticalBar(4, 6, 1);
            plotVerticalBar(12, 5, 0);
            setGridModule(5, 12);
            plotSpigot(0);
            plotSpigot(15);
            plotDataBlock(0, 0, 5, 4, 0, 0);
            plotDataBlock(0, 4, 5, 12, 0, 2);
            plotDataBlock(5, 0, 5, 12, 6, 0);
            plotDataBlock(5, 12, 5, 4, 6, 2);
            break;
        case 2:
            /* Version B */
            plotCentralFinder(8, 4, 1);
            plotVerticalBar(4, 8, 1);
            plotVerticalBar(16, 7, 0);
            setGridModule(7, 16);
            plotSpigot(0);
            plotSpigot(21);
            plotDataBlock(0, 0, 7, 4, 0, 0);
            plotDataBlock(0, 4, 7, 16, 0, 2);
            plotDataBlock(7, 0, 7, 16, 8, 0);
            plotDataBlock(7, 16, 7, 4, 8, 2);
            break;
        case 3:
            /* Version C */
            plotCentralFinder(11, 4, 2);
            plotVerticalBar(4, 11, 1);
            plotVerticalBar(26, 13, 1);
            plotVerticalBar(4, 10, 0);
            plotVerticalBar(26, 10, 0);
            plotSpigot(0);
            plotSpigot(27);
            plotDataBlock(0, 0, 10, 4, 0, 0);
            plotDataBlock(0, 4, 10, 20, 0, 2);
            plotDataBlock(0, 24, 10, 4, 0, 4);
            plotDataBlock(10, 0, 10, 4, 8, 0);
            plotDataBlock(10, 4, 10, 20, 8, 2);
            plotDataBlock(10, 24, 10, 4, 8, 4);
            break;
        case 4:
            /* Version D */
            plotCentralFinder(16, 5, 1);
            plotVerticalBar(4, 16, 1);
            plotVerticalBar(20, 16, 1);
            plotVerticalBar(36, 16, 1);
            plotVerticalBar(4, 15, 0);
            plotVerticalBar(20, 15, 0);
            plotVerticalBar(36, 15, 0);
            plotSpigot(0);
            plotSpigot(12);
            plotSpigot(27);
            plotSpigot(39);
            plotDataBlock(0, 0, 15, 4, 0, 0);
            plotDataBlock(0, 4, 15, 14, 0, 2);
            plotDataBlock(0, 18, 15, 14, 0, 4);
            plotDataBlock(0, 32, 15, 4, 0, 6);
            plotDataBlock(15, 0, 15, 4, 10, 0);
            plotDataBlock(15, 4, 15, 14, 10, 2);
            plotDataBlock(15, 18, 15, 14, 10, 4);
            plotDataBlock(15, 32, 15, 4, 10, 6);
            break;
        case 5:
            /* Version E */
            plotCentralFinder(22, 5, 2);
            plotVerticalBar(4, 22, 1);
            plotVerticalBar(26, 24, 1);
            plotVerticalBar(48, 22, 1);
            plotVerticalBar(4, 21, 0);
            plotVerticalBar(26, 21, 0);
            plotVerticalBar(48, 21, 0);
            plotSpigot(0);
            plotSpigot(12);
            plotSpigot(39);
            plotSpigot(51);
            plotDataBlock(0, 0, 21, 4, 0, 0);
            plotDataBlock(0, 4, 21, 20, 0, 2);
            plotDataBlock(0, 24, 21, 20, 0, 4);
            plotDataBlock(0, 44, 21, 4, 0, 6);
            plotDataBlock(21, 0, 21, 4, 10, 0);
            plotDataBlock(21, 4, 21, 20, 10, 2);
            plotDataBlock(21, 24, 21, 20, 10, 4);
            plotDataBlock(21, 44, 21, 4, 10, 6);
            break;
        case 6:
            /* Version F */
            plotCentralFinder(31, 5, 3);
            plotVerticalBar(4, 31, 1);
            plotVerticalBar(26, 35, 1);
            plotVerticalBar(48, 31, 1);
            plotVerticalBar(70, 35, 1);
            plotVerticalBar(4, 30, 0);
            plotVerticalBar(26, 30, 0);
            plotVerticalBar(48, 30, 0);
            plotVerticalBar(70, 30, 0);
            plotSpigot(0);
            plotSpigot(12);
            plotSpigot(24);
            plotSpigot(45);
            plotSpigot(57);
            plotSpigot(69);
            plotDataBlock(0, 0, 30, 4, 0, 0);
            plotDataBlock(0, 4, 30, 20, 0, 2);
            plotDataBlock(0, 24, 30, 20, 0, 4);
            plotDataBlock(0, 44, 30, 20, 0, 6);
            plotDataBlock(0, 64, 30, 4, 0, 8);
            plotDataBlock(30, 0, 30, 4, 10, 0);
            plotDataBlock(30, 4, 30, 20, 10, 2);
            plotDataBlock(30, 24, 30, 20, 10, 4);
            plotDataBlock(30, 44, 30, 20, 10, 6);
            plotDataBlock(30, 64, 30, 4, 10, 8);
            break;
        case 7:
            /* Version G */
            plotCentralFinder(47, 6, 2);
            plotVerticalBar(6, 47, 1);
            plotVerticalBar(27, 49, 1);
            plotVerticalBar(48, 47, 1);
            plotVerticalBar(69, 49, 1);
            plotVerticalBar(90, 47, 1);
            plotVerticalBar(6, 46, 0);
            plotVerticalBar(27, 46, 0);
            plotVerticalBar(48, 46, 0);
            plotVerticalBar(69, 46, 0);
            plotVerticalBar(90, 46, 0);
            plotSpigot(0);
            plotSpigot(12);
            plotSpigot(24);
            plotSpigot(36);
            plotSpigot(67);
            plotSpigot(79);
            plotSpigot(91);
            plotSpigot(103);
            plotDataBlock(0, 0, 46, 6, 0, 0);
            plotDataBlock(0, 6, 46, 19, 0, 2);
            plotDataBlock(0, 25, 46, 19, 0, 4);
            plotDataBlock(0, 44, 46, 19, 0, 6);
            plotDataBlock(0, 63, 46, 19, 0, 8);
            plotDataBlock(0, 82, 46, 6, 0, 10);
            plotDataBlock(46, 0, 46, 6, 12, 0);
            plotDataBlock(46, 6, 46, 19, 12, 2);
            plotDataBlock(46, 25, 46, 19, 12, 4);
            plotDataBlock(46, 44, 46, 19, 12, 6);
            plotDataBlock(46, 63, 46, 19, 12, 8);
            plotDataBlock(46, 82, 46, 6, 12, 10);
            break;
        case 8:
            /* Version H */
            plotCentralFinder(69, 6, 3);
            plotVerticalBar(6, 69, 1);
            plotVerticalBar(26, 73, 1);
            plotVerticalBar(46, 69, 1);
            plotVerticalBar(66, 73, 1);
            plotVerticalBar(86, 69, 1);
            plotVerticalBar(106, 73, 1);
            plotVerticalBar(126, 69, 1);
            plotVerticalBar(6, 68, 0);
            plotVerticalBar(26, 68, 0);
            plotVerticalBar(46, 68, 0);
            plotVerticalBar(66, 68, 0);
            plotVerticalBar(86, 68, 0);
            plotVerticalBar(106, 68, 0);
            plotVerticalBar(126, 68, 0);
            plotSpigot(0);
            plotSpigot(12);
            plotSpigot(24);
            plotSpigot(36);
            plotSpigot(48);
            plotSpigot(60);
            plotSpigot(87);
            plotSpigot(99);
            plotSpigot(111);
            plotSpigot(123);
            plotSpigot(135);
            plotSpigot(147);
            plotDataBlock(0, 0, 68, 6, 0, 0);
            plotDataBlock(0, 6, 68, 18, 0, 2);
            plotDataBlock(0, 24, 68, 18, 0, 4);
            plotDataBlock(0, 42, 68, 18, 0, 6);
            plotDataBlock(0, 60, 68, 18, 0, 8);
            plotDataBlock(0, 78, 68, 18, 0, 10);
            plotDataBlock(0, 96, 68, 18, 0, 12);
            plotDataBlock(0, 114, 68, 6, 0, 14);
            plotDataBlock(68, 0, 68, 6, 12, 0);
            plotDataBlock(68, 6, 68, 18, 12, 2);
            plotDataBlock(68, 24, 68, 18, 12, 4);
            plotDataBlock(68, 42, 68, 18, 12, 6);
            plotDataBlock(68, 60, 68, 18, 12, 8);
            plotDataBlock(68, 78, 68, 18, 12, 10);
            plotDataBlock(68, 96, 68, 18, 12, 12);
            plotDataBlock(68, 114, 68, 6, 12, 14);
            break;
        case 9:
            /* Version S */
            plotHorizontalBar(5, 1);
            plotHorizontalBar(7, 1);
            setGridModule(6, 0);
            setGridModule(6, symbol_width - 1);
            resetGridModule(7, 1);
            resetGridModule(7, symbol_width - 2);
            switch (sub_version) {
            case 1:
                /* Version S-10 */
                setGridModule(0, 5);
                plotDataBlock(0, 0, 4, 5, 0, 0);
                plotDataBlock(0, 5, 4, 5, 0, 1);
                break;
            case 2:
                /* Version S-20 */
                setGridModule(0, 10);
                setGridModule(4, 10);
                plotDataBlock(0, 0, 4, 10, 0, 0);
                plotDataBlock(0, 10, 4, 10, 0, 1);
                break;
            case 3:
                /* Version S-30 */
                setGridModule(0, 15);
                setGridModule(4, 15);
                setGridModule(6, 15);
                plotDataBlock(0, 0, 4, 15, 0, 0);
                plotDataBlock(0, 15, 4, 15, 0, 1);
                break;
            }
            break;
        case 10:
            /* Version T */
            plotHorizontalBar(11, 1);
            plotHorizontalBar(13, 1);
            plotHorizontalBar(15, 1);
            setGridModule(12, 0);
            setGridModule(12, symbol_width - 1);
            setGridModule(14, 0);
            setGridModule(14, symbol_width - 1);
            resetGridModule(13, 1);
            resetGridModule(13, symbol_width - 2);
            resetGridModule(15, 1);
            resetGridModule(15, symbol_width - 2);
            switch (sub_version) {
            case 1:
                /* Version T-16 */
                setGridModule(0, 8);
                setGridModule(10, 8);
                plotDataBlock(0, 0, 10, 8, 0, 0);
                plotDataBlock(0, 8, 10, 8, 0, 1);
                break;
            case 2:
                /* Version T-32 */
                setGridModule(0, 16);
                setGridModule(10, 16);
                setGridModule(12, 16);
                plotDataBlock(0, 0, 10, 16, 0, 0);
                plotDataBlock(0, 16, 10, 16, 0, 1);
                break;
            case 3:
                /* Verion T-48 */
                setGridModule(0, 24);
                setGridModule(10, 24);
                setGridModule(12, 24);
                setGridModule(14, 24);
                plotDataBlock(0, 0, 10, 24, 0, 0);
                plotDataBlock(0, 24, 10, 24, 0, 1);
                break;
            }
            break;
        }

        readable = "";
        pattern = new String[row_count];
        row_height = new int[row_count];
        for (i = 0; i < row_count; i++) {
            bin.setLength(0);
            for (j = 0; j < symbol_width; j++) {
                if (outputGrid[i][j]) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            pattern[i] = bin2pat(bin);
            row_height[i] = 1;
        }
    }

    private void logCodewords(int count) {
        info("Codewords: ");
        for (int i = 0; i < count; i++) {
            infoSpace(data[i]);
        }
        infoLine();
    }

    private int encodeAsCode1Data() {
        Mode current_mode, next_mode;
        boolean latch;
        boolean done;
        int sourcePoint, targetPoint, i, j;
        int c40_p;
        int text_p;
        int edi_p;
        int byte_start = 0;
        int[] c40_buffer = new int[6];
        int[] text_buffer = new int[6];
        int[] edi_buffer = new int[6];
        String decimal_binary = "";
        int length;
        int shift_set, value;
        int data_left, decimal_count;
        int sub_value;
        int bits_left_in_byte, target_count;
        boolean isTwoDigits;

        inputData = toBytes(content, StandardCharsets.ISO_8859_1);
        length = inputData.length;

        sourcePoint = 0;
        targetPoint = 0;
        c40_p = 0;
        text_p = 0;
        edi_p = 0;

        if (inputDataType == DataType.GS1) {
            data[targetPoint] = 232;
            targetPoint++;
        } /* FNC1 */

        /* Step A */
        current_mode = Mode.C1_ASCII;
        next_mode = Mode.C1_ASCII;

        do {
            if (current_mode != next_mode) {
                /* Change mode */
                switch (next_mode) {
                case C1_C40:
                    data[targetPoint] = 230;
                    targetPoint++;
                    break;
                case C1_TEXT:
                    data[targetPoint] = 239;
                    targetPoint++;
                    break;
                case C1_EDI:
                    data[targetPoint] = 238;
                    targetPoint++;
                    break;
                case C1_BYTE:
                    data[targetPoint] = 231;
                    targetPoint++;
                    break;
                }
            }

            if ((current_mode != Mode.C1_BYTE) && (next_mode == Mode.C1_BYTE)) {
                byte_start = targetPoint;
            }
            current_mode = next_mode;

            if (current_mode == Mode.C1_ASCII) { /* Step B - ASCII encodation */
                next_mode = Mode.C1_ASCII;

                if ((length - sourcePoint) >= 21) { /* Step B1 */
                    j = 0;

                    for (i = 0; i < 21; i++) {
                        if ((inputData[sourcePoint + i] >= '0') && (inputData[sourcePoint + i] <= '9')) {
                            j++;
                        }
                    }

                    if (j == 21) {
                        next_mode = Mode.C1_DECIMAL;
                        decimal_binary += "1111";
                    }
                }

                if ((next_mode == Mode.C1_ASCII) && ((length - sourcePoint) >= 13)) { /* Step B2 */
                    j = 0;

                    for (i = 0; i < 13; i++) {
                        if ((inputData[sourcePoint + i] >= '0') && (inputData[sourcePoint + i] <= '9')) {
                            j++;
                        }
                    }

                    if (j == 13) {
                        latch = false;
                        for (i = sourcePoint + 13; i < length; i++) {
                            if (!((inputData[i] >= '0') &&
                                    (inputData[i] <= '9'))) {
                                latch = true;
                            }
                        }

                        if (!(latch)) {
                            next_mode = Mode.C1_DECIMAL;
                            decimal_binary += "1111";
                        }
                    }
                }

                if (next_mode == Mode.C1_ASCII) { /* Step B3 */
                    isTwoDigits = false;
                    if ((sourcePoint + 1) != length) {
                        if ((inputData[sourcePoint] >= '0') && (inputData[sourcePoint] <= '9')) {
                            if ((inputData[sourcePoint + 1] >= '0') && (inputData[sourcePoint + 1] <= '9')) {
                                // remaining data consists of two numeric digits
                                data[targetPoint] = (10 * (inputData[sourcePoint] - '0'))
                                        + (inputData[sourcePoint + 1] - '0') + 130;
                                targetPoint++;
                                sourcePoint += 2;
                                isTwoDigits = true;
                            }
                        }
                    }

                    if (!(isTwoDigits)) {
                        if (inputData[sourcePoint] == FNC1) {
                            if ((length - sourcePoint) >= 15) { /* Step B4 */
                                j = 0;

                                for (i = 0; i < 15; i++) {
                                    if ((inputData[sourcePoint + i] >= '0')
                                            && (inputData[sourcePoint + i] <= '9')) {
                                        j++;
                                    }
                                }

                                if (j == 15) {
                                    data[targetPoint] = 236; /* FNC1 and change to Decimal */
                                    targetPoint++;
                                    sourcePoint++;
                                    next_mode = Mode.C1_DECIMAL;
                                }
                            }

                            if ((length - sourcePoint) >= 7) { /* Step B5 */
                                j = 0;

                                for (i = 0; i < 7; i++) {
                                    if ((inputData[sourcePoint + i] >= '0')
                                            && (inputData[sourcePoint + i] <= '9')) {
                                        j++;
                                    }
                                }

                                if (j == 7) {
                                    latch = false;
                                    for (i = sourcePoint + 7; i < length; i++) {
                                        if (!((inputData[sourcePoint + i] >= '0')
                                                && (inputData[sourcePoint + i] <= '9'))) {
                                            latch = true;
                                        }
                                    }

                                    if (!(latch)) {
                                        data[targetPoint] = 236; /* FNC1 and change to Decimal */
                                        targetPoint++;
                                        sourcePoint++;
                                        next_mode = Mode.C1_DECIMAL;
                                    }
                                }
                            }
                        }

                        if (next_mode == Mode.C1_ASCII) {

                            /* Step B6 */
                            next_mode = lookAheadTest(length, sourcePoint, current_mode);

                            if (next_mode == Mode.C1_ASCII) {
                                if (inputData[sourcePoint] > 127) {
                                    /* Step B7 */
                                    data[targetPoint] = 235;
                                    targetPoint++; /* FNC4 */
                                    data[targetPoint] = (inputData[sourcePoint] - 128) + 1;
                                    targetPoint++;
                                    sourcePoint++;
                                } else {
                                    /* Step B8 */
                                    if (inputData[sourcePoint] == FNC1) {
                                        data[targetPoint] = 232;
                                        targetPoint++;
                                        sourcePoint++; /* FNC1 */
                                    } else {
                                        data[targetPoint] = inputData[sourcePoint] + 1;
                                        targetPoint++;
                                        sourcePoint++;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (current_mode == Mode.C1_C40) { /* Step C - C40 encodation */
                done = false;
                next_mode = Mode.C1_C40;
                if (c40_p == 0) {
                    if ((length - sourcePoint) >= 12) {
                        j = 0;

                        for (i = 0; i < 12; i++) {
                            if ((inputData[sourcePoint + i] >= '0')
                                    && (inputData[sourcePoint + i] <= '9')) {
                                j++;
                            }
                        }

                        if (j == 12) {
                            next_mode = Mode.C1_ASCII;
                            done = true;
                        }
                    }

                    if ((length - sourcePoint) >= 8) {
                        j = 0;

                        for (i = 0; i < 8; i++) {
                            if ((inputData[sourcePoint + i] >= '0')
                                    && (inputData[sourcePoint + i] <= '9')) {
                                j++;
                            }
                        }

                        if ((length - sourcePoint) == 8) {
                            latch = true;
                        } else {
                            latch = true;
                            for (j = sourcePoint + 8; j < length; j++) {
                                if ((inputData[j] <= '0') || (inputData[j] >= '9')) {
                                    latch = false;
                                }
                            }
                        }

                        if ((j == 8) && latch) {
                            next_mode = Mode.C1_ASCII;
                            done = true;
                        }
                    }

                    if (!(done)) {
                        next_mode = lookAheadTest(length, sourcePoint, current_mode);
                    }
                }

                if (next_mode != Mode.C1_C40) {
                    data[targetPoint] = 255;
                    targetPoint++; /* Unlatch */
                } else {
                    if (inputData[sourcePoint] > 127) {
                        c40_buffer[c40_p] = 1;
                        c40_p++;
                        c40_buffer[c40_p] = 30;
                        c40_p++; /* Upper Shift */
                        shift_set = C40_SHIFT[inputData[sourcePoint] - 128];
                        value = C40_VALUE[inputData[sourcePoint] - 128];
                    } else {
                        shift_set = C40_SHIFT[inputData[sourcePoint]];
                        value = C40_VALUE[inputData[sourcePoint]];
                    }

                    if (inputData[sourcePoint] == FNC1) {
                        shift_set = 2;
                        value = 27; /* FNC1 */
                    }

                    if (shift_set != 0) {
                        c40_buffer[c40_p] = shift_set - 1;
                        c40_p++;
                    }
                    c40_buffer[c40_p] = value;
                    c40_p++;

                    if (c40_p >= 3) {
                        int iv;

                        iv = (1600 * c40_buffer[0]) + (40 * c40_buffer[1])
                                + (c40_buffer[2]) + 1;
                        data[targetPoint] = iv / 256;
                        targetPoint++;
                        data[targetPoint] = iv % 256;
                        targetPoint++;

                        c40_buffer[0] = c40_buffer[3];
                        c40_buffer[1] = c40_buffer[4];
                        c40_buffer[2] = c40_buffer[5];
                        c40_buffer[3] = 0;
                        c40_buffer[4] = 0;
                        c40_buffer[5] = 0;
                        c40_p -= 3;
                    }
                    sourcePoint++;
                }
            }

            if (current_mode == Mode.C1_TEXT) { /* Step D - Text encodation */
                done = false;
                next_mode = Mode.C1_TEXT;
                if (text_p == 0) {
                    if ((length - sourcePoint) >= 12) {
                        j = 0;

                        for (i = 0; i < 12; i++) {
                            if ((inputData[sourcePoint + i] >= '0')
                                    && (inputData[sourcePoint + i] <= '9')) {
                                j++;
                            }
                        }

                        if (j == 12) {
                            next_mode = Mode.C1_ASCII;
                            done = true;
                        }
                    }

                    if ((length - sourcePoint) >= 8) {
                        j = 0;

                        for (i = 0; i < 8; i++) {
                            if ((inputData[sourcePoint + i] >= '0')
                                    && (inputData[sourcePoint + i] <= '9')) {
                                j++;
                            }
                        }

                        if ((length - sourcePoint) == 8) {
                            latch = true;
                        } else {
                            latch = true;
                            for (j = sourcePoint + 8; j < length; j++) {
                                if ((inputData[j] <= '0') || (inputData[j] >= '9')) {
                                    latch = false;
                                }
                            }
                        }

                        if ((j == 8) && latch) {
                            next_mode = Mode.C1_ASCII;
                            done = true;
                        }
                    }

                    if (!(done)) {
                        next_mode = lookAheadTest(length, sourcePoint, current_mode);
                    }
                }

                if (next_mode != Mode.C1_TEXT) {
                    data[targetPoint] = 255;
                    targetPoint++; /* Unlatch */
                } else {
                    if (inputData[sourcePoint] > 127) {
                        text_buffer[text_p] = 1;
                        text_p++;
                        text_buffer[text_p] = 30;
                        text_p++; /* Upper Shift */
                        shift_set = TEXT_SHIFT[inputData[sourcePoint] - 128];
                        value = TEXT_VALUE[inputData[sourcePoint] - 128];
                    } else {
                        shift_set = TEXT_SHIFT[inputData[sourcePoint]];
                        value = TEXT_VALUE[inputData[sourcePoint]];
                    }

                    if (inputData[sourcePoint] == FNC1) {
                        shift_set = 2;
                        value = 27; /* FNC1 */
                    }

                    if (shift_set != 0) {
                        text_buffer[text_p] = shift_set - 1;
                        text_p++;
                    }
                    text_buffer[text_p] = value;
                    text_p++;

                    if (text_p >= 3) {
                        int iv;

                        iv = (1600 * text_buffer[0]) + (40 * text_buffer[1])
                                + (text_buffer[2]) + 1;
                        data[targetPoint] = iv / 256;
                        targetPoint++;
                        data[targetPoint] = iv % 256;
                        targetPoint++;

                        text_buffer[0] = text_buffer[3];
                        text_buffer[1] = text_buffer[4];
                        text_buffer[2] = text_buffer[5];
                        text_buffer[3] = 0;
                        text_buffer[4] = 0;
                        text_buffer[5] = 0;
                        text_p -= 3;
                    }
                    sourcePoint++;
                }
            }

            if (current_mode == Mode.C1_EDI) { /* Step E - EDI Encodation */

                value = 0;
                next_mode = Mode.C1_EDI;
                if (edi_p == 0) {
                    if ((length - sourcePoint) >= 12) {
                        j = 0;

                        for (i = 0; i < 12; i++) {
                            if ((inputData[sourcePoint + i] >= '0')
                                    && (inputData[sourcePoint + i] <= '9')) {
                                j++;
                            }
                        }

                        if (j == 12) {
                            next_mode = Mode.C1_ASCII;
                        }
                    }

                    if ((length - sourcePoint) >= 8) {
                        j = 0;

                        for (i = 0; i < 8; i++) {
                            if ((inputData[sourcePoint + i] >= '0') &&
                                    (inputData[sourcePoint + i] <= '9')) {
                                j++;
                            }
                        }

                        if ((length - sourcePoint) == 8) {
                            latch = true;
                        } else {
                            latch = true;
                            for (j = sourcePoint + 8; j < length; j++) {
                                if ((inputData[j] <= '0') || (inputData[j] >= '9')) {
                                    latch = false;
                                }
                            }
                        }

                        if ((j == 8) && latch) {
                            next_mode = Mode.C1_ASCII;
                        }
                    }

                    if (!((isEdiEncodable(inputData[sourcePoint])
                            && isEdiEncodable(inputData[sourcePoint + 1]))
                            && isEdiEncodable(inputData[sourcePoint + 2]))) {
                        next_mode = Mode.C1_ASCII;
                    }
                }

                if (next_mode != Mode.C1_EDI) {
                    data[targetPoint] = 255;
                    targetPoint++; /* Unlatch */
                } else {
                    if (inputData[sourcePoint] == 13) {
                        value = 0;
                    }
                    if (inputData[sourcePoint] == '*') {
                        value = 1;
                    }
                    if (inputData[sourcePoint] == '>') {
                        value = 2;
                    }
                    if (inputData[sourcePoint] == ' ') {
                        value = 3;
                    }
                    if ((inputData[sourcePoint] >= '0') && (inputData[sourcePoint] <= '9')) {
                        value = inputData[sourcePoint] - '0' + 4;
                    }
                    if ((inputData[sourcePoint] >= 'A') && (inputData[sourcePoint] <= 'Z')) {
                        value = inputData[sourcePoint] - 'A' + 14;
                    }

                    edi_buffer[edi_p] = value;
                    edi_p++;

                    if (edi_p >= 3) {
                        int iv;

                        iv = (1600 * edi_buffer[0]) + (40 * edi_buffer[1])
                                + (edi_buffer[2]) + 1;
                        data[targetPoint] = iv / 256;
                        targetPoint++;
                        data[targetPoint] = iv % 256;
                        targetPoint++;

                        edi_buffer[0] = edi_buffer[3];
                        edi_buffer[1] = edi_buffer[4];
                        edi_buffer[2] = edi_buffer[5];
                        edi_buffer[3] = 0;
                        edi_buffer[4] = 0;
                        edi_buffer[5] = 0;
                        edi_p -= 3;
                    }
                    sourcePoint++;
                }
            }

            if (current_mode == Mode.C1_DECIMAL) { /* Step F - Decimal encodation */

                next_mode = Mode.C1_DECIMAL;

                data_left = length - sourcePoint;
                decimal_count = 0;

                if (data_left >= 1) {
                    if ((inputData[sourcePoint] >= '0') && (inputData[sourcePoint] <= '9')) {
                        decimal_count = 1;
                    }
                }
                if (data_left >= 2) {
                    if ((decimal_count == 1) && ((inputData[sourcePoint + 1] >= '0')
                            && (inputData[sourcePoint + 1] <= '9'))) {
                        decimal_count = 2;
                    }
                }
                if (data_left >= 3) {
                    if ((decimal_count == 2) && ((inputData[sourcePoint + 2] >= '0')
                            && (inputData[sourcePoint + 2] <= '9'))) {
                        decimal_count = 3;
                    }
                }

                if (decimal_count != 3) {

                    /* Finish Decimal mode and go back to ASCII */

                    decimal_binary += "111111"; /* Unlatch */

                    target_count = 3;
                    if (decimal_binary.length() <= 16) {
                        target_count = 2;
                    }
                    if (decimal_binary.length() <= 8) {
                        target_count = 1;
                    }
                    bits_left_in_byte = (8 * target_count) - decimal_binary.length();
                    if (bits_left_in_byte == 8) {
                        bits_left_in_byte = 0;
                    }

                    if (bits_left_in_byte == 2) {
                        decimal_binary += "01";
                    }

                    if ((bits_left_in_byte == 4) || (bits_left_in_byte == 6)) {
                        if (decimal_count >= 1) {
                            sub_value = inputData[sourcePoint] - '0' + 1;

                            for (i = 0x08; i > 0; i = i >> 1) {
                                if ((sub_value & i) != 0) {
                                    decimal_binary += "1";
                                } else {
                                    decimal_binary += "0";
                                }
                            }
                            sourcePoint++;
                        } else {
                            decimal_binary += "1111";
                        }
                    }

                    if (bits_left_in_byte == 6) {
                        decimal_binary += "01";
                    }

                    /* Binary buffer is full - transfer to data */
                    if (target_count >= 1) {
                        for(i = 0; i < 8; i++) {
                            if(decimal_binary.charAt(i) == '1') {
                                data[targetPoint] += 128 >> i;
                            }
                        }
                        targetPoint++;
                    }
                    if (target_count >= 2) {
                        for(i = 0; i < 8; i++) {
                            if(decimal_binary.charAt(8 + i) == '1') {
                                data[targetPoint] += 128 >> i;
                            }

                        }
                        targetPoint++;
                    }
                    if (target_count == 3) {
                        for(i = 0; i < 8; i++) {
                            if(decimal_binary.charAt(16 + i) == '1') {
                                data[targetPoint] += 128 >> i;
                            }

                        }
                        targetPoint++;
                    }

                    next_mode = Mode.C1_ASCII;
                } else {
                    /* There are three digits - convert the value to binary */
                    value = (100 * (inputData[sourcePoint] - '0'))
                            + (10 * (inputData[sourcePoint + 1] - '0'))
                            + (inputData[sourcePoint + 2] - '0') + 1;

                    for (i = 0x200; i > 0; i = i >> 1) {
                        if ((value & i) != 0) {
                            decimal_binary += "1";
                        } else {
                            decimal_binary += "0";
                        }
                    }
                    sourcePoint += 3;
                }

                if (decimal_binary.length() >= 24) {
                    /* Binary buffer is full - transfer to data */
                    for(i = 0; i < 8; i++) {
                        if (decimal_binary.charAt(i) == '1') {
                            data[targetPoint] += 128 >> i;
                        }

                        if (decimal_binary.charAt(8 + i) == '1') {
                            data[targetPoint + 1] += 128 >> i;
                        }

                        if (decimal_binary.charAt(16 + i) == '1') {
                            data[targetPoint + 2] += 128 >> i;
                        }

                    }
                    targetPoint += 3;

                    if (decimal_binary.length() > 24) {
                        decimal_binary = decimal_binary.substring(24);
                    }
                }
            }

            if (current_mode == Mode.C1_BYTE) {
                next_mode = Mode.C1_BYTE;

                if (inputData[sourcePoint] == FNC1) {
                    next_mode = Mode.C1_ASCII;
                } else {
                    if (inputData[sourcePoint] <= 127) {
                        next_mode = lookAheadTest(length, sourcePoint, current_mode);
                    }
                }

                if (next_mode != Mode.C1_BYTE) {
                    /* Insert byte field length */
                    if ((targetPoint - byte_start) <= 249) {
                        for (i = targetPoint; i >= byte_start; i--) {
                            data[i + 1] = data[i];
                        }
                        data[byte_start] = (targetPoint - byte_start);
                        targetPoint++;
                    } else {
                        for (i = targetPoint; i >= byte_start; i--) {
                            data[i + 2] = data[i];
                        }
                        data[byte_start] = 249 + ((targetPoint - byte_start) / 250);
                        data[byte_start + 1] = ((targetPoint - byte_start) % 250);
                        targetPoint += 2;
                    }
                } else {
                    data[targetPoint] = inputData[sourcePoint];
                    targetPoint++;
                    sourcePoint++;
                }
            }

            if (targetPoint > 1480) {
                /* Data is too large for symbol */
                throw new OkapiException("Input data too long");
            }

        } while (sourcePoint < length);

        /* Empty buffers */
        if (c40_p == 2) {
            int iv;

            c40_buffer[2] = 1;
            iv = (1600 * c40_buffer[0]) + (40 * c40_buffer[1])
                    + (c40_buffer[2]) + 1;
            data[targetPoint] = iv / 256;
            targetPoint++;
            data[targetPoint] = iv % 256;
            targetPoint++;
            data[targetPoint] = 255;
            targetPoint++; /* Unlatch */
        }
        if (c40_p == 1) {
            int iv;

            c40_buffer[1] = 1;
            c40_buffer[2] = 31; /* Pad */
            iv = (1600 * c40_buffer[0]) + (40 * c40_buffer[1])
                    + (c40_buffer[2]) + 1;
            data[targetPoint] = iv / 256;
            targetPoint++;
            data[targetPoint] = iv % 256;
            targetPoint++;
            data[targetPoint] = 255;
            targetPoint++; /* Unlatch */
        }
        if (text_p == 2) {
            int iv;

            text_buffer[2] = 1;
            iv = (1600 * text_buffer[0]) + (40 * text_buffer[1])
                    + (text_buffer[2]) + 1;
            data[targetPoint] = iv / 256;
            targetPoint++;
            data[targetPoint] = iv % 256;
            targetPoint++;
            data[targetPoint] = 255;
            targetPoint++; /* Unlatch */
        }
        if (text_p == 1) {
            int iv;

            text_buffer[1] = 1;
            text_buffer[2] = 31; /* Pad */
            iv = (1600 * text_buffer[0]) + (40 * text_buffer[1])
                    + (text_buffer[2]) + 1;
            data[targetPoint] = iv / 256;
            targetPoint++;
            data[targetPoint] = iv % 256;
            targetPoint++;
            data[targetPoint] = 255;
            targetPoint++; /* Unlatch */
        }

        if (current_mode == Mode.C1_DECIMAL) {
            /* Finish Decimal mode and go back to ASCII */

            decimal_binary += "111111"; /* Unlatch */

            target_count = 3;
            if (decimal_binary.length() <= 16) {
                target_count = 2;
            }
            if (decimal_binary.length() <= 8) {
                target_count = 1;
            }
            bits_left_in_byte = (8 * target_count) - decimal_binary.length();
            if (bits_left_in_byte == 8) {
                bits_left_in_byte = 0;
            }

            if (bits_left_in_byte == 2) {
                decimal_binary += "01";
            }

            if ((bits_left_in_byte == 4) || (bits_left_in_byte == 6)) {
                decimal_binary += "1111";
            }

            if (bits_left_in_byte == 6) {
                decimal_binary += "01";
            }

            /* Binary buffer is full - transfer to data */
            if (target_count >= 1) {
                for(i = 0; i < 8; i++) {
                    if(decimal_binary.charAt(i) == '1') {
                        data[targetPoint] += 128 >> i;
                    }
                }
                targetPoint++;
            }
            if (target_count >= 2) {
                for(i = 0; i < 8; i++) {
                    if(decimal_binary.charAt(8 + i) == '1') {
                        data[targetPoint] += 128 >> i;
                    }
                }
                targetPoint++;
            }
            if (target_count == 3) {
                for(i = 0; i < 8; i++) {
                    if(decimal_binary.charAt(16 + i) == '1') {
                        data[targetPoint] += 128 >> i;
                    }
                }
                targetPoint++;
            }
        }

        if (current_mode == Mode.C1_BYTE) {
            /* Insert byte field length */
            if ((targetPoint - byte_start) <= 249) {
                for (i = targetPoint; i >= byte_start; i--) {
                    data[i + 1] = data[i];
                }
                data[byte_start] = (targetPoint - byte_start);
                targetPoint++;
            } else {
                for (i = targetPoint; i >= byte_start; i--) {
                    data[i + 2] = data[i];
                }
                data[byte_start] = 249 + ((targetPoint - byte_start) / 250);
                data[byte_start + 1] = ((targetPoint - byte_start) % 250);
                targetPoint += 2;
            }
        }

        /* Re-check length of data */
        if (targetPoint > 1480) {
            /* Data is too large for symbol */
            throw new OkapiException("Input data too long");
        }

        return targetPoint;
    }

    private Mode lookAheadTest(int sourcelen, int position,
            Mode current_mode) {
        double ascii_count, c40_count, text_count, edi_count, byte_count;
        int reduced_char;
        int done, best_count, sp;
        Mode best_scheme;

        /* Step J */
        if (current_mode == Mode.C1_ASCII) {
            ascii_count = 0.0;
            c40_count = 1.0;
            text_count = 1.0;
            edi_count = 1.0;
            byte_count = 2.0;
        } else {
            ascii_count = 1.0;
            c40_count = 2.0;
            text_count = 2.0;
            edi_count = 2.0;
            byte_count = 3.0;
        }

        switch (current_mode) {
        case C1_C40:
            c40_count = 0.0;
            break;
        case C1_TEXT:
            text_count = 0.0;
            break;
        case C1_BYTE:
            byte_count = 0.0;
            break;
        case C1_EDI:
            edi_count = 0.0;
            break;
        }

        for (sp = position;
        (sp < sourcelen) && (sp <= (position + 8)); sp++) {

            if (inputData[sp] <= 127) {
                reduced_char = inputData[sp];
            } else {
                reduced_char = inputData[sp] - 127;
            }

            /* Step L */
            if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) {
                ascii_count += 0.5;
            } else {
                ascii_count = roundUpToNextInteger(ascii_count);
                if (inputData[sp] > 127) {
                    ascii_count += 2.0;
                } else {
                    ascii_count += 1.0;
                }
            }

            /* Step M */
            done = 0;
            if (reduced_char == ' ') {
                c40_count += (2.0 / 3.0);
                done = 1;
            }
            if ((reduced_char >= '0') && (reduced_char <= '9')) {
                c40_count += (2.0 / 3.0);
                done = 1;
            }
            if ((reduced_char >= 'A') && (reduced_char <= 'Z')) {
                c40_count += (2.0 / 3.0);
                done = 1;
            }
            if (inputData[sp] > 127) {
                c40_count += (4.0 / 3.0);
            }
            if (done == 0) {
                c40_count += (4.0 / 3.0);
            }

            /* Step N */
            done = 0;
            if (reduced_char == ' ') {
                text_count += (2.0 / 3.0);
                done = 1;
            }
            if ((reduced_char >= '0') && (reduced_char <= '9')) {
                text_count += (2.0 / 3.0);
                done = 1;
            }
            if ((reduced_char >= 'a') && (reduced_char <= 'z')) {
                text_count += (2.0 / 3.0);
                done = 1;
            }
            if (inputData[sp] > 127) {
                text_count += (4.0 / 3.0);
            }
            if (done == 0) {
                text_count += (4.0 / 3.0);
            }

            /* Step O */
            done = 0;
            if (inputData[sp] == 13) {
                edi_count += (2.0 / 3.0);
                done = 1;
            }
            if (inputData[sp] == '*') {
                edi_count += (2.0 / 3.0);
                done = 1;
            }
            if (inputData[sp] == '>') {
                edi_count += (2.0 / 3.0);
                done = 1;
            }
            if (inputData[sp] == ' ') {
                edi_count += (2.0 / 3.0);
                done = 1;
            }
            if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) {
                edi_count += (2.0 / 3.0);
                done = 1;
            }
            if ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')) {
                edi_count += (2.0 / 3.0);
                done = 1;
            }
            if (inputData[sp] > 127) {
                edi_count += (13.0 / 3.0);
            } else {
                if (done == 0) {
                    edi_count += (10.0 / 3.0);
                }
            }

            /* Step P */
            if (inputData[sp] == FNC1) {
                byte_count += 3.0;
            } else {
                byte_count += 1.0;
            }

        }

        ascii_count = roundUpToNextInteger(ascii_count);
        c40_count = roundUpToNextInteger(c40_count);
        text_count = roundUpToNextInteger(text_count);
        edi_count = roundUpToNextInteger(edi_count);
        byte_count = roundUpToNextInteger(byte_count);
        best_scheme = Mode.C1_ASCII;

        if (sp == sourcelen) {
            /* Step K */
            best_count = (int) edi_count;

            if (text_count <= best_count) {
                best_count = (int) text_count;
                best_scheme = Mode.C1_TEXT;
            }

            if (c40_count <= best_count) {
                best_count = (int) c40_count;
                best_scheme = Mode.C1_C40;
            }

            if (ascii_count <= best_count) {
                best_count = (int) ascii_count;
                best_scheme = Mode.C1_ASCII;
            }

            if (byte_count <= best_count) {
                best_scheme = Mode.C1_BYTE;
            }
        } else {
            /* Step Q */

            if (((edi_count + 1.0 <= ascii_count)
                    && (edi_count + 1.0 <= c40_count))
                    && ((edi_count + 1.0 <= byte_count)
                    && (edi_count + 1.0 <= text_count))) {
                best_scheme = Mode.C1_EDI;
            }

            if ((c40_count + 1.0 <= ascii_count)
                    && (c40_count + 1.0 <= text_count)) {

                if (c40_count < edi_count) {
                    best_scheme = Mode.C1_C40;
                } else {
                    if (c40_count == edi_count) {
                        if (preferEdi(sourcelen, position)) {
                            best_scheme = Mode.C1_EDI;
                        } else {
                            best_scheme = Mode.C1_C40;
                        }
                    }
                }
            }

            if (((text_count + 1.0 <= ascii_count)
                    && (text_count + 1.0 <= c40_count))
                    && ((text_count + 1.0 <= byte_count)
                    && (text_count + 1.0 <= edi_count))) {
                best_scheme = Mode.C1_TEXT;
            }

            if (((ascii_count + 1.0 <= byte_count)
                    && (ascii_count + 1.0 <= c40_count))
                    && ((ascii_count + 1.0 <= text_count)
                    && (ascii_count + 1.0 <= edi_count))) {
                best_scheme = Mode.C1_ASCII;
            }

            if (((byte_count + 1.0 <= ascii_count)
                    && (byte_count + 1.0 <= c40_count))
                    && ((byte_count + 1.0 <= text_count)
                    && (byte_count + 1.0 <= edi_count))) {
                best_scheme = Mode.C1_BYTE;
            }
        }

        return best_scheme;
    }

    private double roundUpToNextInteger(double input) {
        double fraction, output;

        fraction = input - (int) input;
        if (fraction > 0.01) {
            output = (input - fraction) + 1.0;
        } else {
            output = input;
        }

        return output;
    }

    private boolean preferEdi(int sourcelen, int position) {
        int i;

        for (i = position; isEdiEncodable(inputData[position + i])
                && ((position + i) < sourcelen); i++);

        if ((position + i) == sourcelen) {
            /* Reached end of input */
            return false;
        }

        if (inputData[position + i - 1] == 13) {
            return true;
        }
        if (inputData[position + i - 1] == '*') {
            return true;
        }
        if (inputData[position + i - 1] == '>') {
            return true;
        }

        return false;
    }

    private boolean isEdiEncodable(int input) {
        boolean result = false;

        if (input == 13) {
            result = true;
        }
        if (input == '*') {
            result = true;
        }
        if (input == '>') {
            result = true;
        }
        if (input == ' ') {
            result = true;
        }
        if ((input >= '0') && (input <= '9')) {
            result = true;
        }
        if ((input >= 'A') && (input <= 'Z')) {
            result = true;
        }

        return result;
    }

    private void plotCentralFinder(int start_row, int row_count, int full_rows) {
        for (int i = 0; i < row_count; i++) {
            if (i < full_rows) {
                plotHorizontalBar(start_row + (i * 2), 1);
            } else {
                plotHorizontalBar(start_row + (i * 2), 0);
                if (i != row_count - 1) {
                    setGridModule(start_row + (i * 2) + 1, 1);
                    setGridModule(start_row + (i * 2) + 1, symbol_width - 2);
                }
            }
        }
    }

    private void plotHorizontalBar(int row_no, int full) {
        if (full != 0) {
            for (int i = 0; i < symbol_width; i++) {
                setGridModule(row_no, i);
            }
        } else {
            for (int i = 1; i < symbol_width - 1; i++) {
                setGridModule(row_no, i);
            }
        }
    }

    private void plotVerticalBar(int column, int height, int top) {
        if (top != 0) {
            for (int i = 0; i < height; i++) {
                setGridModule(i, column);
            }
        } else {
            for (int i = 0; i < height; i++) {
                setGridModule(row_count - i - 1, column);
            }
        }
    }

    private void plotSpigot(int row_no) {
        for (int i = symbol_width - 1; i > 0; i--) {
            if (outputGrid[row_no][i - 1]) {
                setGridModule(row_no, i);
            }
        }
    }

    private void plotDataBlock(int start_row, int start_col, int height, int width, int row_offset, int col_offset) {
        for (int i = start_row; i < (start_row + height); i++) {
            for (int j = start_col; j < (start_col + width); j++) {
                if (datagrid[i][j] == '1') {
                    setGridModule(i + row_offset, j + col_offset);
                }
            }
        }
    }

    private void setGridModule(int row, int column) {
        outputGrid[row][column] = true;
    }

    private void resetGridModule(int row, int column) {
        outputGrid[row][column] = false;
    }

    private static int getSize(Version version) {
        switch(version) {
            case A:
                return 1;
            case B:
                return 2;
            case C:
                return 3;
            case D:
                return 4;
            case E:
                return 5;
            case F:
                return 6;
            case G:
                return 7;
            case H:
                return 8;
            default:
                return 0;
        }
    }
}
 public class Composite extends Symbol {

    /** The linear component choices available. */
    public static enum LinearEncoding {
        UPCA, UPCE, EAN, CODE_128, DATABAR_14, DATABAR_14_STACK,
        DATABAR_14_STACK_OMNI, DATABAR_LIMITED, DATABAR_EXPANDED,
        DATABAR_EXPANDED_STACK
    }

    /** The 2D component choices available. */
    public static enum CompositeMode {
        /**
         * Indicates that the composite symbol uses a MicroPDF417 variant as the 2D component.
         * Of the 2D component choices, this one holds the least amount of data.
         */
        CC_A,
        /**
         * Indicates that the composite symbol uses a MicroPDF417 symbol as the 2D component, starting with a codeword of 920.
         */
        CC_B,
        /**
         * Indicates that the composite symbol uses a PDF417 symbol as the 2D component, starting with a codeword of 920.
         * Of the 2D component choices, this one holds the most amount of data. May only be used if the linear component
         * is {@link LinearEncoding#CODE_128 Code 128}.
         */
        CC_C
    }

    private static enum GeneralFieldMode {
        NUMERIC, ALPHA, ISOIEC, INVALID_CHAR, ANY_ENC, ALPHA_OR_ISO
    }

    /* CC-A component coefficients from ISO/IEC 24728:2006 Annex F */
    private static final int[] CCA_COEFFS = {
        /* k = 4 */
        522, 568, 723, 809,
        /* k = 5 */
        427, 919, 460, 155, 566,
        /* k = 6 */
        861, 285, 19, 803, 17, 766,
        /* k = 7 */
        76, 925, 537, 597, 784, 691, 437,
        /* k = 8 */
        237, 308, 436, 284, 646, 653, 428, 379
    };

    private static final int[] COEFRS = {
        /* k = 2 */
        27, 917,
        /* k = 4 */
        522, 568, 723, 809,
        /* k = 8 */
        237, 308, 436, 284, 646, 653, 428, 379,
        /* k = 16 */
        274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,
        /* k = 32 */
        361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517,
        273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410,
        /* k = 64 */
        539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877, 381, 612,
        723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511, 400, 672, 762, 283, 184,
        440, 35, 519, 31, 460, 594, 225, 535, 517, 352, 605, 158, 651, 201, 488, 502,
        648, 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843, 623, 264, 543,
        /* k = 128 */
        521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925, 749, 415,
        822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631, 292, 908, 490, 704,
        516, 258, 457, 907, 594, 723, 674, 292, 272, 96, 684, 432, 686, 606, 860, 569,
        193, 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712, 463, 646, 776,
        171, 491, 297, 763, 156, 732, 95, 270, 447, 90, 507, 48, 228, 821, 808, 898,
        784, 663, 627, 378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616,
        157, 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587, 804, 34,
        211, 330, 539, 297, 827, 865, 37, 517, 834, 315, 550, 86, 801, 4, 108, 539,
        /* k = 256 */
        524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786, 138, 720,
        858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194, 280, 201, 280, 828, 757,
        710, 814, 919, 89, 68, 569, 11, 204, 796, 605, 540, 913, 801, 700, 799, 137,
        439, 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284, 549, 209, 884,
        315, 70, 329, 793, 490, 274, 877, 162, 749, 812, 684, 461, 334, 376, 849, 521,
        307, 291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470,
        637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136, 538, 906, 90,
        2, 290, 743, 199, 655, 903, 329, 49, 802, 580, 355, 588, 188, 462, 10, 134,
        628, 320, 479, 130, 739, 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234,
        722, 384, 177, 752, 607, 640, 455, 193, 689, 707, 805, 641, 48, 60, 732, 621,
        895, 544, 261, 852, 655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528,
        503, 118, 49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550,
        73, 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791, 893, 754,
        605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299, 922, 191, 910, 532,
        609, 829, 189, 20, 167, 29, 872, 449, 83, 402, 41, 656, 505, 579, 481, 173,
        404, 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648, 55, 497, 10,
        /* k = 512 */
        352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380, 350, 492,
        197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88, 87, 193, 352, 781,
        846, 75, 327, 520, 435, 543, 203, 666, 249, 346, 781, 621, 640, 268, 794, 534,
        539, 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858, 916, 552, 41,
        542, 289, 122, 272, 383, 800, 485, 98, 752, 472, 761, 107, 784, 860, 658, 741,
        290, 204, 681, 407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142,
        808, 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516, 258,
        240, 518, 794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328, 596, 786, 303,
        570, 381, 415, 641, 156, 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402,
        40, 708, 575, 162, 864, 229, 65, 861, 841, 512, 164, 477, 221, 92, 358, 785,
        288, 357, 850, 836, 827, 736, 707, 94, 8, 494, 114, 521, 2, 499, 851, 543,
        152, 729, 771, 95, 248, 361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820,
        669, 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578,
        37, 124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578, 911,
        283, 711, 472, 420, 245, 288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408,
        842, 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713, 159, 672, 729,
        624, 59, 193, 417, 158, 209, 563, 564, 343, 693, 109, 608, 563, 365, 181, 772,
        677, 310, 248, 353, 708, 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777,
        618, 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45,
        787, 680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644, 905,
        789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341,
        242, 797, 838, 837, 720, 224, 307, 631, 61, 87, 560, 310, 756, 665, 397, 808,
        851, 309, 473, 795, 378, 31, 647, 915, 459, 806, 590, 731, 425, 216, 548, 249,
        321, 881, 699, 535, 673, 782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791,
        660, 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437,
        375, 273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687, 842,
        37, 357, 720, 742, 330, 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316,
        342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721, 610, 46, 656,
        447, 171, 616, 464, 190, 531, 297, 321, 762, 752, 533, 175, 134, 14, 381, 433,
        717, 45, 111, 20, 596, 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780,
        407, 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768, 223, 849, 647,
        63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263
    };

    /* rows, error codewords, k-offset of valid CC-A sizes from ISO/IEC 24723:2006 Table 9 */
    private static final int[] CCA_VARIANTS = {
        5, 6, 7, 8, 9, 10, 12, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 4, 4, 5, 5, 6, 6, 7, 4, 5, 6, 7, 7, 4, 5, 6, 7, 8, 0, 0, 4, 4, 9, 9, 15, 0, 4, 9, 15, 15, 0, 4, 9, 15, 22
    };

    /* following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24723:2006 tables 10 and 11 */
    private static final int[] A_RAP_TABLE = {
        39, 1, 32, 8, 14, 43, 20, 11, 1, 5, 15, 21, 40, 43, 46, 34, 29, 0, 0, 0, 0, 0, 0, 0, 43, 33, 37, 47, 1, 20, 23, 26, 14, 9, 19, 33, 12, 40, 46, 23, 52, 23, 13, 17, 27, 33, 52, 3, 6, 46, 41, 6, 0, 3, 3, 3, 0, 3, 3, 0, 3, 6, 6, 0, 0, 0, 0, 3
    };

    private static final String[] CODAGEMC = {
        "urA", "xfs", "ypy", "unk", "xdw", "yoz", "pDA", "uls", "pBk", "eBA",
        "pAs", "eAk", "prA", "uvs", "xhy", "pnk", "utw", "xgz", "fDA", "pls", "fBk", "frA", "pvs",
        "uxy", "fnk", "ptw", "uwz", "fls", "psy", "fvs", "pxy", "ftw", "pwz", "fxy", "yrx", "ufk",
        "xFw", "ymz", "onA", "uds", "xEy", "olk", "ucw", "dBA", "oks", "uci", "dAk", "okg", "dAc",
        "ovk", "uhw", "xaz", "dnA", "ots", "ugy", "dlk", "osw", "ugj", "dks", "osi", "dvk", "oxw",
        "uiz", "dts", "owy", "dsw", "owj", "dxw", "oyz", "dwy", "dwj", "ofA", "uFs", "xCy", "odk",
        "uEw", "xCj", "clA", "ocs", "uEi", "ckk", "ocg", "ckc", "ckE", "cvA", "ohs", "uay", "ctk",
        "ogw", "uaj", "css", "ogi", "csg", "csa", "cxs", "oiy", "cww", "oij", "cwi", "cyy", "oFk",
        "uCw", "xBj", "cdA", "oEs", "uCi", "cck", "oEg", "uCb", "ccc", "oEa", "ccE", "oED", "chk",
        "oaw", "uDj", "cgs", "oai", "cgg", "oab", "cga", "cgD", "obj", "cib", "cFA", "oCs", "uBi",
        "cEk", "oCg", "uBb", "cEc", "oCa", "cEE", "oCD", "cEC", "cas", "cag", "caa", "cCk", "uAr",
        "oBa", "oBD", "cCB", "tfk", "wpw", "yez", "mnA", "tds", "woy", "mlk", "tcw", "woj", "FBA",
        "mks", "FAk", "mvk", "thw", "wqz", "FnA", "mts", "tgy", "Flk", "msw", "Fks", "Fkg", "Fvk",
        "mxw", "tiz", "Fts", "mwy", "Fsw", "Fsi", "Fxw", "myz", "Fwy", "Fyz", "vfA", "xps", "yuy",
        "vdk", "xow", "yuj", "qlA", "vcs", "xoi", "qkk", "vcg", "xob", "qkc", "vca", "mfA", "tFs",
        "wmy", "qvA", "mdk", "tEw", "wmj", "qtk", "vgw", "xqj", "hlA", "Ekk", "mcg", "tEb", "hkk",
        "qsg", "hkc", "EvA", "mhs", "tay", "hvA", "Etk", "mgw", "taj", "htk", "qww", "vij", "hss",
        "Esg", "hsg", "Exs", "miy", "hxs", "Eww", "mij", "hww", "qyj", "hwi", "Eyy", "hyy", "Eyj",
        "hyj", "vFk", "xmw", "ytj", "qdA", "vEs", "xmi", "qck", "vEg", "xmb", "qcc", "vEa", "qcE",
        "qcC", "mFk", "tCw", "wlj", "qhk", "mEs", "tCi", "gtA", "Eck", "vai", "tCb", "gsk", "Ecc",
        "mEa", "gsc", "qga", "mED", "EcC", "Ehk", "maw", "tDj", "gxk", "Egs", "mai", "gws", "qii",
        "mab", "gwg", "Ega", "EgD", "Eiw", "mbj", "gyw", "Eii", "gyi", "Eib", "gyb", "gzj", "qFA",
        "vCs", "xli", "qEk", "vCg", "xlb", "qEc", "vCa", "qEE", "vCD", "qEC", "qEB", "EFA", "mCs",
        "tBi", "ghA", "EEk", "mCg", "tBb", "ggk", "qag", "vDb", "ggc", "EEE", "mCD", "ggE", "qaD",
        "ggC", "Eas", "mDi", "gis", "Eag", "mDb", "gig", "qbb", "gia", "EaD", "giD", "gji", "gjb",
        "qCk", "vBg", "xkr", "qCc", "vBa", "qCE", "vBD", "qCC", "qCB", "ECk", "mBg", "tAr", "gak",
        "ECc", "mBa", "gac", "qDa", "mBD", "gaE", "ECC", "gaC", "ECB", "EDg", "gbg", "gba", "gbD",
        "vAq", "vAn", "qBB", "mAq", "EBE", "gDE", "gDC", "gDB", "lfA", "sps", "wey", "ldk", "sow",
        "ClA", "lcs", "soi", "Ckk", "lcg", "Ckc", "CkE", "CvA", "lhs", "sqy", "Ctk", "lgw", "sqj",
        "Css", "lgi", "Csg", "Csa", "Cxs", "liy", "Cww", "lij", "Cwi", "Cyy", "Cyj", "tpk", "wuw",
        "yhj", "ndA", "tos", "wui", "nck", "tog", "wub", "ncc", "toa", "ncE", "toD", "lFk", "smw",
        "wdj", "nhk", "lEs", "smi", "atA", "Cck", "tqi", "smb", "ask", "ngg", "lEa", "asc", "CcE",
        "asE", "Chk", "law", "snj", "axk", "Cgs", "trj", "aws", "nii", "lab", "awg", "Cga", "awa",
        "Ciw", "lbj", "ayw", "Cii", "ayi", "Cib", "Cjj", "azj", "vpA", "xus", "yxi", "vok", "xug",
        "yxb", "voc", "xua", "voE", "xuD", "voC", "nFA", "tms", "wti", "rhA", "nEk", "xvi", "wtb",
        "rgk", "vqg", "xvb", "rgc", "nEE", "tmD", "rgE", "vqD", "nEB", "CFA", "lCs", "sli", "ahA",
        "CEk", "lCg", "slb", "ixA", "agk", "nag", "tnb", "iwk", "rig", "vrb", "lCD", "iwc", "agE",
        "naD", "iwE", "CEB", "Cas", "lDi", "ais", "Cag", "lDb", "iys", "aig", "nbb", "iyg", "rjb",
        "CaD", "aiD", "Cbi", "aji", "Cbb", "izi", "ajb", "vmk", "xtg", "ywr", "vmc", "xta", "vmE",
        "xtD", "vmC", "vmB", "nCk", "tlg", "wsr", "rak", "nCc", "xtr", "rac", "vna", "tlD", "raE",
        "nCC", "raC", "nCB", "raB", "CCk", "lBg", "skr", "aak", "CCc", "lBa", "iik", "aac", "nDa",
        "lBD", "iic", "rba", "CCC", "iiE", "aaC", "CCB", "aaB", "CDg", "lBr", "abg", "CDa", "ijg",
        "aba", "CDD", "ija", "abD", "CDr", "ijr", "vlc", "xsq", "vlE", "xsn", "vlC", "vlB", "nBc",
        "tkq", "rDc", "nBE", "tkn", "rDE", "vln", "rDC", "nBB", "rDB", "CBc", "lAq", "aDc", "CBE",
        "lAn", "ibc", "aDE", "nBn", "ibE", "rDn", "CBB", "ibC", "aDB", "ibB", "aDq", "ibq", "ibn",
        "xsf", "vkl", "tkf", "nAm", "nAl", "CAo", "aBo", "iDo", "CAl", "aBl", "kpk", "BdA", "kos",
        "Bck", "kog", "seb", "Bcc", "koa", "BcE", "koD", "Bhk", "kqw", "sfj", "Bgs", "kqi", "Bgg",
        "kqb", "Bga", "BgD", "Biw", "krj", "Bii", "Bib", "Bjj", "lpA", "sus", "whi", "lok", "sug",
        "loc", "sua", "loE", "suD", "loC", "BFA", "kms", "sdi", "DhA", "BEk", "svi", "sdb", "Dgk",
        "lqg", "svb", "Dgc", "BEE", "kmD", "DgE", "lqD", "BEB", "Bas", "kni", "Dis", "Bag", "knb",
        "Dig", "lrb", "Dia", "BaD", "Bbi", "Dji", "Bbb", "Djb", "tuk", "wxg", "yir", "tuc", "wxa",
        "tuE", "wxD", "tuC", "tuB", "lmk", "stg", "nqk", "lmc", "sta", "nqc", "tva", "stD", "nqE",
        "lmC", "nqC", "lmB", "nqB", "BCk", "klg", "Dak", "BCc", "str", "bik", "Dac", "lna", "klD",
        "bic", "nra", "BCC", "biE", "DaC", "BCB", "DaB", "BDg", "klr", "Dbg", "BDa", "bjg", "Dba",
        "BDD", "bja", "DbD", "BDr", "Dbr", "bjr", "xxc", "yyq", "xxE", "yyn", "xxC", "xxB", "ttc",
        "wwq", "vvc", "xxq", "wwn", "vvE", "xxn", "vvC", "ttB", "vvB", "llc", "ssq", "nnc", "llE",
        "ssn", "rrc", "nnE", "ttn", "rrE", "vvn", "llB", "rrC", "nnB", "rrB", "BBc", "kkq", "DDc",
        "BBE", "kkn", "bbc", "DDE", "lln", "jjc", "bbE", "nnn", "BBB", "jjE", "rrn", "DDB", "jjC",
        "BBq", "DDq", "BBn", "bbq", "DDn", "jjq", "bbn", "jjn", "xwo", "yyf", "xwm", "xwl", "tso",
        "wwf", "vto", "xwv", "vtm", "tsl", "vtl", "lko", "ssf", "nlo", "lkm", "rno", "nlm", "lkl",
        "rnm", "nll", "rnl", "BAo", "kkf", "DBo", "lkv", "bDo", "DBm", "BAl", "jbo", "bDm", "DBl",
        "jbm", "bDl", "jbl", "DBv", "jbv", "xwd", "vsu", "vst", "nku", "rlu", "rlt", "DAu", "bBu",
        "jDu", "jDt", "ApA", "Aok", "keg", "Aoc", "AoE", "AoC", "Aqs", "Aqg", "Aqa", "AqD", "Ari",
        "Arb", "kuk", "kuc", "sha", "kuE", "shD", "kuC", "kuB", "Amk", "kdg", "Bqk", "kvg", "kda",
        "Bqc", "kva", "BqE", "kvD", "BqC", "AmB", "BqB", "Ang", "kdr", "Brg", "kvr", "Bra", "AnD",
        "BrD", "Anr", "Brr", "sxc", "sxE", "sxC", "sxB", "ktc", "lvc", "sxq", "sgn", "lvE", "sxn",
        "lvC", "ktB", "lvB", "Alc", "Bnc", "AlE", "kcn", "Drc", "BnE", "AlC", "DrE", "BnC", "AlB",
        "DrC", "BnB", "Alq", "Bnq", "Aln", "Drq", "Bnn", "Drn", "wyo", "wym", "wyl", "swo", "txo",
        "wyv", "txm", "swl", "txl", "kso", "sgf", "lto", "swv", "nvo", "ltm", "ksl", "nvm", "ltl",
        "nvl", "Ako", "kcf", "Blo", "ksv", "Dno", "Blm", "Akl", "bro", "Dnm", "Bll", "brm", "Dnl",
        "Akv", "Blv", "Dnv", "brv", "yze", "yzd", "wye", "xyu", "wyd", "xyt", "swe", "twu", "swd",
        "vxu", "twt", "vxt", "kse", "lsu", "ksd", "ntu", "lst", "rvu", "ypk", "zew", "xdA", "yos",
        "zei", "xck", "yog", "zeb", "xcc", "yoa", "xcE", "yoD", "xcC", "xhk", "yqw", "zfj", "utA",
        "xgs", "yqi", "usk", "xgg", "yqb", "usc", "xga", "usE", "xgD", "usC", "uxk", "xiw", "yrj",
        "ptA", "uws", "xii", "psk", "uwg", "xib", "psc", "uwa", "psE", "uwD", "psC", "pxk", "uyw",
        "xjj", "ftA", "pws", "uyi", "fsk", "pwg", "uyb", "fsc", "pwa", "fsE", "pwD", "fxk", "pyw",
        "uzj", "fws", "pyi", "fwg", "pyb", "fwa", "fyw", "pzj", "fyi", "fyb", "xFA", "yms", "zdi",
        "xEk", "ymg", "zdb", "xEc", "yma", "xEE", "ymD", "xEC", "xEB", "uhA", "xas", "yni", "ugk",
        "xag", "ynb", "ugc", "xaa", "ugE", "xaD", "ugC", "ugB", "oxA", "uis", "xbi", "owk", "uig",
        "xbb", "owc", "uia", "owE", "uiD", "owC", "owB", "dxA", "oys", "uji", "dwk", "oyg", "ujb",
        "dwc", "oya", "dwE", "oyD", "dwC", "dys", "ozi", "dyg", "ozb", "dya", "dyD", "dzi", "dzb",
        "xCk", "ylg", "zcr", "xCc", "yla", "xCE", "ylD", "xCC", "xCB", "uak", "xDg", "ylr", "uac",
        "xDa", "uaE", "xDD", "uaC", "uaB", "oik", "ubg", "xDr", "oic", "uba", "oiE", "ubD", "oiC",
        "oiB", "cyk", "ojg", "ubr", "cyc", "oja", "cyE", "ojD", "cyC", "cyB", "czg", "ojr", "cza",
        "czD", "czr", "xBc", "ykq", "xBE", "ykn", "xBC", "xBB", "uDc", "xBq", "uDE", "xBn", "uDC",
        "uDB", "obc", "uDq", "obE", "uDn", "obC", "obB", "cjc", "obq", "cjE", "obn", "cjC", "cjB",
        "cjq", "cjn", "xAo", "ykf", "xAm", "xAl", "uBo", "xAv", "uBm", "uBl", "oDo", "uBv", "oDm",
        "oDl", "cbo", "oDv", "cbm", "cbl", "xAe", "xAd", "uAu", "uAt", "oBu", "oBt", "wpA", "yes",
        "zFi", "wok", "yeg", "zFb", "woc", "yea", "woE", "yeD", "woC", "woB", "thA", "wqs", "yfi",
        "tgk", "wqg", "yfb", "tgc", "wqa", "tgE", "wqD", "tgC", "tgB", "mxA", "tis", "wri", "mwk",
        "tig", "wrb", "mwc", "tia", "mwE", "tiD", "mwC", "mwB", "FxA", "mys", "tji", "Fwk", "myg",
        "tjb", "Fwc", "mya", "FwE", "myD", "FwC", "Fys", "mzi", "Fyg", "mzb", "Fya", "FyD", "Fzi",
        "Fzb", "yuk", "zhg", "hjs", "yuc", "zha", "hbw", "yuE", "zhD", "hDy", "yuC", "yuB", "wmk",
        "ydg", "zEr", "xqk", "wmc", "zhr", "xqc", "yva", "ydD", "xqE", "wmC", "xqC", "wmB", "xqB",
        "tak", "wng", "ydr", "vik", "tac", "wna", "vic", "xra", "wnD", "viE", "taC", "viC", "taB",
        "viB", "mik", "tbg", "wnr", "qyk", "mic", "tba", "qyc", "vja", "tbD", "qyE", "miC", "qyC",
        "miB", "qyB", "Eyk", "mjg", "tbr", "hyk", "Eyc", "mja", "hyc", "qza", "mjD", "hyE", "EyC",
        "hyC", "EyB", "Ezg", "mjr", "hzg", "Eza", "hza", "EzD", "hzD", "Ezr", "ytc", "zgq", "grw",
        "ytE", "zgn", "gny", "ytC", "glz", "ytB", "wlc", "ycq", "xnc", "wlE", "ycn", "xnE", "ytn",
        "xnC", "wlB", "xnB", "tDc", "wlq", "vbc", "tDE", "wln", "vbE", "xnn", "vbC", "tDB", "vbB",
        "mbc", "tDq", "qjc", "mbE", "tDn", "qjE", "vbn", "qjC", "mbB", "qjB", "Ejc", "mbq", "gzc",
        "EjE", "mbn", "gzE", "qjn", "gzC", "EjB", "gzB", "Ejq", "gzq", "Ejn", "gzn", "yso", "zgf",
        "gfy", "ysm", "gdz", "ysl", "wko", "ycf", "xlo", "ysv", "xlm", "wkl", "xll", "tBo", "wkv",
        "vDo", "tBm", "vDm", "tBl", "vDl", "mDo", "tBv", "qbo", "vDv", "qbm", "mDl", "qbl", "Ebo",
        "mDv", "gjo", "Ebm", "gjm", "Ebl", "gjl", "Ebv", "gjv", "yse", "gFz", "ysd", "wke", "xku",
        "wkd", "xkt", "tAu", "vBu", "tAt", "vBt", "mBu", "qDu", "mBt", "qDt", "EDu", "gbu", "EDt",
        "gbt", "ysF", "wkF", "xkh", "tAh", "vAx", "mAx", "qBx", "wek", "yFg", "zCr", "wec", "yFa",
        "weE", "yFD", "weC", "weB", "sqk", "wfg", "yFr", "sqc", "wfa", "sqE", "wfD", "sqC", "sqB",
        "lik", "srg", "wfr", "lic", "sra", "liE", "srD", "liC", "liB", "Cyk", "ljg", "srr", "Cyc",
        "lja", "CyE", "ljD", "CyC", "CyB", "Czg", "ljr", "Cza", "CzD", "Czr", "yhc", "zaq", "arw",
        "yhE", "zan", "any", "yhC", "alz", "yhB", "wdc", "yEq", "wvc", "wdE", "yEn", "wvE", "yhn",
        "wvC", "wdB", "wvB", "snc", "wdq", "trc", "snE", "wdn", "trE", "wvn", "trC", "snB", "trB",
        "lbc", "snq", "njc", "lbE", "snn", "njE", "trn", "njC", "lbB", "njB", "Cjc", "lbq", "azc",
        "CjE", "lbn", "azE", "njn", "azC", "CjB", "azB", "Cjq", "azq", "Cjn", "azn", "zio", "irs",
        "rfy", "zim", "inw", "rdz", "zil", "ily", "ikz", "ygo", "zaf", "afy", "yxo", "ziv", "ivy",
        "adz", "yxm", "ygl", "itz", "yxl", "wco", "yEf", "wto", "wcm", "xvo", "yxv", "wcl", "xvm",
        "wtl", "xvl", "slo", "wcv", "tno", "slm", "vro", "tnm", "sll", "vrm", "tnl", "vrl", "lDo",
        "slv", "nbo", "lDm", "rjo", "nbm", "lDl", "rjm", "nbl", "rjl", "Cbo", "lDv", "ajo", "Cbm",
        "izo", "ajm", "Cbl", "izm", "ajl", "izl", "Cbv", "ajv", "zie", "ifw", "rFz", "zid", "idy",
        "icz", "yge", "aFz", "ywu", "ygd", "ihz", "ywt", "wce", "wsu", "wcd", "xtu", "wst", "xtt",
        "sku", "tlu", "skt", "vnu", "tlt", "vnt", "lBu", "nDu", "lBt", "rbu", "nDt", "rbt", "CDu",
        "abu", "CDt", "iju", "abt", "ijt", "ziF", "iFy", "iEz", "ygF", "ywh", "wcF", "wsh", "xsx",
        "skh", "tkx", "vlx", "lAx", "nBx", "rDx", "CBx", "aDx", "ibx", "iCz", "wFc", "yCq", "wFE",
        "yCn", "wFC", "wFB", "sfc", "wFq", "sfE", "wFn", "sfC", "sfB", "krc", "sfq", "krE", "sfn",
        "krC", "krB", "Bjc", "krq", "BjE", "krn", "BjC", "BjB", "Bjq", "Bjn", "yao", "zDf", "Dfy",
        "yam", "Ddz", "yal", "wEo", "yCf", "who", "wEm", "whm", "wEl", "whl", "sdo", "wEv", "svo",
        "sdm", "svm", "sdl", "svl", "kno", "sdv", "lro", "knm", "lrm", "knl", "lrl", "Bbo", "knv",
        "Djo", "Bbm", "Djm", "Bbl", "Djl", "Bbv", "Djv", "zbe", "bfw", "npz", "zbd", "bdy", "bcz",
        "yae", "DFz", "yiu", "yad", "bhz", "yit", "wEe", "wgu", "wEd", "wxu", "wgt", "wxt", "scu",
        "stu", "sct", "tvu", "stt", "tvt", "klu", "lnu", "klt", "nru", "lnt", "nrt", "BDu", "Dbu",
        "BDt", "bju", "Dbt", "bjt", "jfs", "rpy", "jdw", "roz", "jcy", "jcj", "zbF", "bFy", "zjh",
        "jhy", "bEz", "jgz", "yaF", "yih", "yyx", "wEF", "wgh", "wwx", "xxx", "sch", "ssx", "ttx",
        "vvx", "kkx", "llx", "nnx", "rrx", "BBx", "DDx", "bbx", "jFw", "rmz", "jEy", "jEj", "bCz",
        "jaz", "jCy", "jCj", "jBj", "wCo", "wCm", "wCl", "sFo", "wCv", "sFm", "sFl", "kfo", "sFv",
        "kfm", "kfl", "Aro", "kfv", "Arm", "Arl", "Arv", "yDe", "Bpz", "yDd", "wCe", "wau", "wCd",
        "wat", "sEu", "shu", "sEt", "sht", "kdu", "kvu", "kdt", "kvt", "Anu", "Bru", "Ant", "Brt",
        "zDp", "Dpy", "Doz", "yDF", "ybh", "wCF", "wah", "wix", "sEh", "sgx", "sxx", "kcx", "ktx",
        "lvx", "Alx", "Bnx", "Drx", "bpw", "nuz", "boy", "boj", "Dmz", "bqz", "jps", "ruy", "jow",
        "ruj", "joi", "job", "bmy", "jqy", "bmj", "jqj", "jmw", "rtj", "jmi", "jmb", "blj", "jnj",
        "jli", "jlb", "jkr", "sCu", "sCt", "kFu", "kFt", "Afu", "Aft", "wDh", "sCh", "sax", "kEx",
        "khx", "Adx", "Avx", "Buz", "Duy", "Duj", "buw", "nxj", "bui", "bub", "Dtj", "bvj", "jus",
        "rxi", "jug", "rxb", "jua", "juD", "bti", "jvi", "btb", "jvb", "jtg", "rwr", "jta", "jtD",
        "bsr", "jtr", "jsq", "jsn", "Bxj", "Dxi", "Dxb", "bxg", "nyr", "bxa", "bxD", "Dwr", "bxr",
        "bwq", "bwn", "pjk", "urw", "ejA", "pbs", "uny", "ebk", "pDw", "ulz", "eDs", "pBy", "eBw",
        "zfc", "fjk", "prw", "zfE", "fbs", "pny", "zfC", "fDw", "plz", "zfB", "fBy", "yrc", "zfq",
        "frw", "yrE", "zfn", "fny", "yrC", "flz", "yrB", "xjc", "yrq", "xjE", "yrn", "xjC", "xjB",
        "uzc", "xjq", "uzE", "xjn", "uzC", "uzB", "pzc", "uzq", "pzE", "uzn", "pzC", "djA", "ors",
        "ufy", "dbk", "onw", "udz", "dDs", "oly", "dBw", "okz", "dAy", "zdo", "drs", "ovy", "zdm",
        "dnw", "otz", "zdl", "dly", "dkz", "yno", "zdv", "dvy", "ynm", "dtz", "ynl", "xbo", "ynv",
        "xbm", "xbl", "ujo", "xbv", "ujm", "ujl", "ozo", "ujv", "ozm", "ozl", "crk", "ofw", "uFz",
        "cns", "ody", "clw", "ocz", "cky", "ckj", "zcu", "cvw", "ohz", "zct", "cty", "csz", "ylu",
        "cxz", "ylt", "xDu", "xDt", "ubu", "ubt", "oju", "ojt", "cfs", "oFy", "cdw", "oEz", "ccy",
        "ccj", "zch", "chy", "cgz", "ykx", "xBx", "uDx", "cFw", "oCz", "cEy", "cEj", "caz", "cCy",
        "cCj", "FjA", "mrs", "tfy", "Fbk", "mnw", "tdz", "FDs", "mly", "FBw", "mkz", "FAy", "zFo",
        "Frs", "mvy", "zFm", "Fnw", "mtz", "zFl", "Fly", "Fkz", "yfo", "zFv", "Fvy", "yfm", "Ftz",
        "yfl", "wro", "yfv", "wrm", "wrl", "tjo", "wrv", "tjm", "tjl", "mzo", "tjv", "mzm", "mzl",
        "qrk", "vfw", "xpz", "hbA", "qns", "vdy", "hDk", "qlw", "vcz", "hBs", "qky", "hAw", "qkj",
        "hAi", "Erk", "mfw", "tFz", "hrk", "Ens", "mdy", "hns", "qty", "mcz", "hlw", "Eky", "hky",
        "Ekj", "hkj", "zEu", "Evw", "mhz", "zhu", "zEt", "hvw", "Ety", "zht", "hty", "Esz", "hsz",
        "ydu", "Exz", "yvu", "ydt", "hxz", "yvt", "wnu", "xru", "wnt", "xrt", "tbu", "vju", "tbt",
        "vjt", "mju", "mjt", "grA", "qfs", "vFy", "gnk", "qdw", "vEz", "gls", "qcy", "gkw", "qcj",
        "gki", "gkb", "Efs", "mFy", "gvs", "Edw", "mEz", "gtw", "qgz", "gsy", "Ecj", "gsj", "zEh",
        "Ehy", "zgx", "gxy", "Egz", "gwz", "ycx", "ytx", "wlx", "xnx", "tDx", "vbx", "mbx", "gfk",
        "qFw", "vCz", "gds", "qEy", "gcw", "qEj", "gci", "gcb", "EFw", "mCz", "ghw", "EEy", "ggy",
        "EEj", "ggj", "Eaz", "giz", "gFs", "qCy", "gEw", "qCj", "gEi", "gEb", "ECy", "gay", "ECj",
        "gaj", "gCw", "qBj", "gCi", "gCb", "EBj", "gDj", "gBi", "gBb", "Crk", "lfw", "spz", "Cns",
        "ldy", "Clw", "lcz", "Cky", "Ckj", "zCu", "Cvw", "lhz", "zCt", "Cty", "Csz", "yFu", "Cxz",
        "yFt", "wfu", "wft", "sru", "srt", "lju", "ljt", "arA", "nfs", "tpy", "ank", "ndw", "toz",
        "als", "ncy", "akw", "ncj", "aki", "akb", "Cfs", "lFy", "avs", "Cdw", "lEz", "atw", "ngz",
        "asy", "Ccj", "asj", "zCh", "Chy", "zax", "axy", "Cgz", "awz", "yEx", "yhx", "wdx", "wvx",
        "snx", "trx", "lbx", "rfk", "vpw", "xuz", "inA", "rds", "voy", "ilk", "rcw", "voj", "iks",
        "rci", "ikg", "rcb", "ika", "afk", "nFw", "tmz", "ivk", "ads", "nEy", "its", "rgy", "nEj",
        "isw", "aci", "isi", "acb", "isb", "CFw", "lCz", "ahw", "CEy", "ixw", "agy", "CEj", "iwy",
        "agj", "iwj", "Caz", "aiz", "iyz", "ifA", "rFs", "vmy", "idk", "rEw", "vmj", "ics", "rEi",
        "icg", "rEb", "ica", "icD", "aFs", "nCy", "ihs", "aEw", "nCj", "igw", "raj", "igi", "aEb",
        "igb", "CCy", "aay", "CCj", "iiy", "aaj", "iij", "iFk", "rCw", "vlj", "iEs", "rCi", "iEg",
        "rCb", "iEa", "iED", "aCw", "nBj", "iaw", "aCi", "iai", "aCb", "iab", "CBj", "aDj", "ibj",
        "iCs", "rBi", "iCg", "rBb", "iCa", "iCD", "aBi", "iDi", "aBb", "iDb", "iBg", "rAr", "iBa",
        "iBD", "aAr", "iBr", "iAq", "iAn", "Bfs", "kpy", "Bdw", "koz", "Bcy", "Bcj", "Bhy", "Bgz",
        "yCx", "wFx", "sfx", "krx", "Dfk", "lpw", "suz", "Dds", "loy", "Dcw", "loj", "Dci", "Dcb",
        "BFw", "kmz", "Dhw", "BEy", "Dgy", "BEj", "Dgj", "Baz", "Diz", "bfA", "nps", "tuy", "bdk",
        "now", "tuj", "bcs", "noi", "bcg", "nob", "bca", "bcD", "DFs", "lmy", "bhs", "DEw", "lmj",
        "bgw", "DEi", "bgi", "DEb", "bgb", "BCy", "Day", "BCj", "biy", "Daj", "bij", "rpk", "vuw",
        "xxj", "jdA", "ros", "vui", "jck", "rog", "vub", "jcc", "roa", "jcE", "roD", "jcC", "bFk",
        "nmw", "ttj", "jhk", "bEs", "nmi", "jgs", "rqi", "nmb", "jgg", "bEa", "jga", "bED", "jgD",
        "DCw", "llj", "baw", "DCi", "jiw", "bai", "DCb", "jii", "bab", "jib", "BBj", "DDj", "bbj",
        "jjj", "jFA", "rms", "vti", "jEk", "rmg", "vtb", "jEc", "rma", "jEE", "rmD", "jEC", "jEB",
        "bCs", "nli", "jas", "bCg", "nlb", "jag", "rnb", "jaa", "bCD", "jaD", "DBi", "bDi", "DBb",
        "jbi", "bDb", "jbb", "jCk", "rlg", "vsr", "jCc", "rla", "jCE", "rlD", "jCC", "jCB", "bBg",
        "nkr", "jDg", "bBa", "jDa", "bBD", "jDD", "DAr", "bBr", "jDr", "jBc", "rkq", "jBE", "rkn",
        "jBC", "jBB", "bAq", "jBq", "bAn", "jBn", "jAo", "rkf", "jAm", "jAl", "bAf", "jAv", "Apw",
        "kez", "Aoy", "Aoj", "Aqz", "Bps", "kuy", "Bow", "kuj", "Boi", "Bob", "Amy", "Bqy", "Amj",
        "Bqj", "Dpk", "luw", "sxj", "Dos", "lui", "Dog", "lub", "Doa", "DoD", "Bmw", "ktj", "Dqw",
        "Bmi", "Dqi", "Bmb", "Dqb", "Alj", "Bnj", "Drj", "bpA", "nus", "txi", "bok", "nug", "txb",
        "boc", "nua", "boE", "nuD", "boC", "boB", "Dms", "lti", "bqs", "Dmg", "ltb", "bqg", "nvb",
        "bqa", "DmD", "bqD", "Bli", "Dni", "Blb", "bri", "Dnb", "brb", "ruk", "vxg", "xyr", "ruc",
        "vxa", "ruE", "vxD", "ruC", "ruB", "bmk", "ntg", "twr", "jqk", "bmc", "nta", "jqc", "rva",
        "ntD", "jqE", "bmC", "jqC", "bmB", "jqB", "Dlg", "lsr", "bng", "Dla", "jrg", "bna", "DlD",
        "jra", "bnD", "jrD", "Bkr", "Dlr", "bnr", "jrr", "rtc", "vwq", "rtE", "vwn", "rtC", "rtB",
        "blc", "nsq", "jnc", "blE", "nsn", "jnE", "rtn", "jnC", "blB", "jnB", "Dkq", "blq", "Dkn",
        "jnq", "bln", "jnn", "rso", "vwf", "rsm", "rsl", "bko", "nsf", "jlo", "bkm", "jlm", "bkl",
        "jll", "Dkf", "bkv", "jlv", "rse", "rsd", "bke", "jku", "bkd", "jkt", "Aey", "Aej", "Auw",
        "khj", "Aui", "Aub", "Adj", "Avj", "Bus", "kxi", "Bug", "kxb", "Bua", "BuD", "Ati", "Bvi",
        "Atb", "Bvb", "Duk", "lxg", "syr", "Duc", "lxa", "DuE", "lxD", "DuC", "DuB", "Btg", "kwr",
        "Dvg", "lxr", "Dva", "BtD", "DvD", "Asr", "Btr", "Dvr", "nxc", "tyq", "nxE", "tyn", "nxC",
        "nxB", "Dtc", "lwq", "bvc", "nxq", "lwn", "bvE", "DtC", "bvC", "DtB", "bvB", "Bsq", "Dtq",
        "Bsn", "bvq", "Dtn", "bvn", "vyo", "xzf", "vym", "vyl", "nwo", "tyf", "rxo", "nwm", "rxm",
        "nwl", "rxl", "Dso", "lwf", "bto", "Dsm", "jvo", "btm", "Dsl", "jvm", "btl", "jvl", "Bsf",
        "Dsv", "btv", "jvv", "vye", "vyd", "nwe", "rwu", "nwd", "rwt", "Dse", "bsu", "Dsd", "jtu",
        "bst", "jtt", "vyF", "nwF", "rwh", "DsF", "bsh", "jsx", "Ahi", "Ahb", "Axg", "kir", "Axa",
        "AxD", "Agr", "Axr", "Bxc", "kyq", "BxE", "kyn", "BxC", "BxB", "Awq", "Bxq", "Awn", "Bxn",
        "lyo", "szf", "lym", "lyl", "Bwo", "kyf", "Dxo", "lyv", "Dxm", "Bwl", "Dxl", "Awf", "Bwv",
        "Dxv", "tze", "tzd", "lye", "nyu", "lyd", "nyt", "Bwe", "Dwu", "Bwd", "bxu", "Dwt", "bxt",
        "tzF", "lyF", "nyh", "BwF", "Dwh", "bwx", "Aiq", "Ain", "Ayo", "kjf", "Aym", "Ayl", "Aif",
        "Ayv", "kze", "kzd", "Aye", "Byu", "Ayd", "Byt", "szp"
    };

    private static final char[] BR_SET = {
        'A', 'B', 'C', 'D', 'E', 'F', '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[] PDF_TTF = {
        "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111",
        "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001",
        "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010",
        "11011", "11100", "11101", "11110", "11111", "01", "1111111101010100", "11111101000101001"
    };

    /* Left and Right Row Address Pattern from Table 2 */
    private static final String[] RAPLR = {
        "", "221311", "311311", "312211", "222211", "213211", "214111", "223111",
        "313111", "322111", "412111", "421111", "331111", "241111", "232111", "231211", "321211",
        "411211", "411121", "411112", "321112", "312112", "311212", "311221", "311131", "311122",
        "311113", "221113", "221122", "221131", "221221", "222121", "312121", "321121", "231121",
        "231112", "222112", "213112", "212212", "212221", "212131", "212122", "212113", "211213",
        "211123", "211132", "211141", "211231", "211222", "211312", "211321", "211411", "212311"
    };

    /* Centre Row Address Pattern from Table 2 */
    private static final String[] RAPC = {
        "", "112231", "121231", "122131", "131131", "131221", "132121", "141121",
        "141211", "142111", "133111", "132211", "131311", "122311", "123211", "124111", "115111",
        "114211", "114121", "123121", "123112", "122212", "122221", "121321", "121411", "112411",
        "113311", "113221", "113212", "113122", "122122", "131122", "131113", "122113", "113113",
        "112213", "112222", "112312", "112321", "111421", "111331", "111322", "111232", "111223",
        "111133", "111124", "111214", "112114", "121114", "121123", "121132", "112132", "112141"
    };

    private static final int[] MICRO_VARIANTS = {
        1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
        11, 14, 17, 20, 24, 28, 8, 11, 14, 17, 20, 23, 26, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, 4, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44,
        7, 7, 7, 8, 8, 8, 8, 9, 9, 10, 11, 13, 15, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, 8, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50,
        0, 0, 0, 7, 7, 7, 7, 15, 15, 24, 34, 57, 84, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294, 7, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294
    };

    /* rows, columns, error codewords, k-offset */
    /* MicroPDF417 coefficients from ISO/IEC 24728:2006 Annex F */
    private static final int[] MICROCOEFFS = {
        /* k = 7 */
        76, 925, 537, 597, 784, 691, 437,
        /* k = 8 */
        237, 308, 436, 284, 646, 653, 428, 379,
        /* k = 9 */
        567, 527, 622, 257, 289, 362, 501, 441, 205,
        /* k = 10 */
        377, 457, 64, 244, 826, 841, 818, 691, 266, 612,
        /* k = 11 */
        462, 45, 565, 708, 825, 213, 15, 68, 327, 602, 904,
        /* k = 12 */
        597, 864, 757, 201, 646, 684, 347, 127, 388, 7, 69, 851,
        /* k = 13 */
        764, 713, 342, 384, 606, 583, 322, 592, 678, 204, 184, 394, 692,
        /* k = 14 */
        669, 677, 154, 187, 241, 286, 274, 354, 478, 915, 691, 833, 105, 215,
        /* k = 15 */
        460, 829, 476, 109, 904, 664, 230, 5, 80, 74, 550, 575, 147, 868, 642,
        /* k = 16 */
        274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,
        /* k = 18 */
        279, 577, 315, 624, 37, 855, 275, 739, 120, 297, 312, 202, 560, 321, 233, 756,
        760, 573,
        /* k = 21 */
        108, 519, 781, 534, 129, 425, 681, 553, 422, 716, 763, 693, 624, 610, 310, 691,
        347, 165, 193, 259, 568,
        /* k = 26 */
        443, 284, 887, 544, 788, 93, 477, 760, 331, 608, 269, 121, 159, 830, 446, 893,
        699, 245, 441, 454, 325, 858, 131, 847, 764, 169,
        /* k = 32 */
        361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517,
        273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410,
        /* k = 38 */
        234, 228, 438, 848, 133, 703, 529, 721, 788, 322, 280, 159, 738, 586, 388, 684,
        445, 680, 245, 595, 614, 233, 812, 32, 284, 658, 745, 229, 95, 689, 920, 771,
        554, 289, 231, 125, 117, 518,
        /* k = 44 */
        476, 36, 659, 848, 678, 64, 764, 840, 157, 915, 470, 876, 109, 25, 632, 405,
        417, 436, 714, 60, 376, 97, 413, 706, 446, 21, 3, 773, 569, 267, 272, 213,
        31, 560, 231, 758, 103, 271, 572, 436, 339, 730, 82, 285,
        /* k = 50 */
        923, 797, 576, 875, 156, 706, 63, 81, 257, 874, 411, 416, 778, 50, 205, 303,
        188, 535, 909, 155, 637, 230, 534, 96, 575, 102, 264, 233, 919, 593, 865, 26,
        579, 623, 766, 146, 10, 739, 246, 127, 71, 244, 211, 477, 920, 876, 427, 820,
        718, 435
    };

    /* following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24728:2006 tables 10, 11 and 12 */
    private static final int[] RAP_TABLE = {
        1, 8, 36, 19, 9, 25, 1, 1, 8, 36, 19, 9, 27, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, 47, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, 19, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25,
        9, 8, 36, 19, 17, 33, 1, 9, 8, 36, 19, 17, 35, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, 43, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49,
        0, 3, 6, 0, 6, 0, 0, 0, 3, 6, 0, 6, 6, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 3, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0
    };

    private StringBuilder binary_string;
    private int ecc;
    private LinearEncoding symbology = LinearEncoding.CODE_128;
    private int[] general_field;
    private GeneralFieldMode[] general_field_type;
    private int cc_width;
    private int[][] pwr928 = new int[69][7];
    private int[] codeWords = new int[180];
    private int codeWordCount;
    private int[] bitStr = new int[13];
    private int[] inputData;
    private CompositeMode cc_mode;
    private DataType linearDataType;
    private String linearContent;
    private CompositeMode userPreferredMode = CompositeMode.CC_A;
    private int target_bitsize;
    private int remainder;
    private int linearWidth; // width of Code 128 linear
    private Integer guardPatternExtraHeight; // for UPC and EAN only
    private int separatorHeight = 1; // for all except UPC and EAN
    private Integer preferredColumns; // for DataBar Expanded Stacked only

    public Composite() {
        inputDataType = Symbol.DataType.GS1;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    /**
     * Set the type of linear component included in the composite symbol,
     * this will determine how the lower part of the symbol is encoded.
     *
     * @param linearSymbology The symbology of the linear component
     */
    public void setSymbology(LinearEncoding linearSymbology) {
        symbology = linearSymbology;
    }

    /**
     * Returns the type of linear component included in the composite symbol.
     *
     * @return the type of linear component included in the composite symbol
     */
    public LinearEncoding getSymbology() {
        return symbology;
    }

    /**
     * Sets the data type of the linear component. Note that not all data types
     * are valid for all linear component types.
     *
     * @param linearDataType the data type of the linear component
     */
    public void setLinearDataType(DataType linearDataType) {
        this.linearDataType = linearDataType;
    }

    /**
     * Returns the data type of the linear component.
     *
     * @return the data type of the linear component
     */
    public DataType getLinearDataType() {
        return linearDataType;
    }

    /**
     * Set the data to be encoded in the linear component of the composite symbol.
     *
     * @param linearContent the linear data in GS1 format
     */
    public void setLinearContent(String linearContent) {
        this.linearContent = linearContent;
    }

    /**
     * Returns the data encoded in the linear component of the composite symbol.
     *
     * @return the data encoded in the linear component of the composite symbol
     */
    public String getLinearContent() {
        return linearContent;
    }

    /**
     * Set the preferred encoding method for the 2D component of the composite symbol.
     * This value may be ignored if the amount of data supplied is too big for the selected
     * encoding. Mode CC-C can only be used with a Code 128 linear component.
     *
     * @param userMode Preferred mode
     */
    public void setPreferredMode(CompositeMode userMode) {
        userPreferredMode = userMode;
    }

    /**
     * Returns the preferred encoding method for the 2D component of the composite symbol.
     *
     * @return the preferred encoding method for the 2D component of the composite symbol
     */
    public CompositeMode getPreferredMode() {
        return userPreferredMode;
    }

    /**
     * Returns the actual 2D component encoding method that was used.
     * Only available after {@link #setContent(String)} has been called.
     *
     * @return the actual 2D component encoding method that was used
     */
    public CompositeMode getModeUsed() {
        return cc_mode;
    }

    /**
     * Sets the extra height used for the guard patterns. Used only when the linear component is
     * {@link LinearEncoding#EAN}, {@link LinearEncoding#UPCA} or {@link LinearEncoding#UPCE}.
     *
     * @param guardPatternExtraHeight the extra height used for the guard patterns
     */
    public void setGuardPatternExtraHeight(int guardPatternExtraHeight) {
        this.guardPatternExtraHeight = guardPatternExtraHeight;
    }

    /**
     * Sets the separator height to use between the linear component and the 2D component. Used
     * for all types of linear components <b>except</b> {@link LinearEncoding#EAN},
     * {@link LinearEncoding#UPCA} or {@link LinearEncoding#UPCE}. The default value is {@code 1}.
     *
     * @param separatorHeight the separator height to use between the linear component and the 2D component
     */
    public void setSeparatorHeight(int separatorHeight) {
        if (separatorHeight < 1) {
            throw new IllegalArgumentException("Invalid Composite separator height: " + separatorHeight);
        }
        this.separatorHeight = separatorHeight;
    }

    /**
     * Sets the preferred width of a stacked symbol by selecting the number of "columns" or symbol
     * segments in each row of data. Valid only when using a {@link LinearEncoding#DATABAR_EXPANDED_STACK}
     * linear component.
     *
     * @param columns the number of segments in each row
     */
    public void setPreferredColumns(int columns) {
        if (columns < 1 || columns > 10) {
            throw new IllegalArgumentException("Invalid column count: " + columns);
        }
        this.preferredColumns = columns;
    }

    @Override
    protected void encode() {

        List < Rectangle2D.Double > combine_rect = new ArrayList<>();
        List < TextBox > combine_txt = new ArrayList<>();
        int top_shift = 0; // 2D component x-coordinate shift
        int bottom_shift = 0; // linear component x-coordinate shift
        linearWidth = 0;

        if (linearContent.isEmpty()) {
            throw new OkapiException("No linear data set");
        }

        // Manage composite component encoding first
        encodeComposite();

        // Then encode linear component
        Symbol linear;
        switch (symbology) {
            case UPCA:
                Upc upca = new Upc();
                upca.setMode(Upc.Mode.UPCA);
                upca.setLinkageFlag(true);
                if (guardPatternExtraHeight != null) {
                    upca.setGuardPatternExtraHeight(guardPatternExtraHeight);
                }
                linear = upca;
                bottom_shift = 6;
                top_shift = 3;
                break;
            case UPCE:
                Upc upce = new Upc();
                upce.setMode(Upc.Mode.UPCE);
                upce.setLinkageFlag(true);
                if (guardPatternExtraHeight != null) {
                    upce.setGuardPatternExtraHeight(guardPatternExtraHeight);
                }
                linear = upce;
                bottom_shift = 6;
                top_shift = 3;
                break;
            case EAN:
                Ean ean = new Ean();
                if (eanCalculateVersion() == 8) {
                    ean.setMode(Ean.Mode.EAN8);
                    bottom_shift = (cc_mode == CompositeMode.CC_A ? 4 : 14); // left row address pattern omitted in 3-column CC-A
                } else {
                    ean.setMode(Ean.Mode.EAN13);
                    bottom_shift = 6;
                    top_shift = 3;
                }
                ean.setLinkageFlag(true);
                if (guardPatternExtraHeight != null) {
                    ean.setGuardPatternExtraHeight(guardPatternExtraHeight);
                }
                linear = ean;
                break;
            case CODE_128:
                Code128 code128 = new Code128();
                switch (cc_mode) {
                    case CC_A:
                        code128.setCca();
                        break;
                    case CC_B:
                        code128.setCcb();
                        break;
                    case CC_C:
                        code128.setCcc();
                        bottom_shift = 7;
                        break;
                }
                code128.setDataType(Symbol.DataType.GS1);
                linear = code128;
                break;
            case DATABAR_14:
                DataBar14 dataBar14 = new DataBar14();
                dataBar14.setLinkageFlag(true);
                dataBar14.setMode(Mode.LINEAR);
                linear = dataBar14;
                bottom_shift = 4;
                break;
            case DATABAR_14_STACK_OMNI:
                DataBar14 dataBar14SO = new DataBar14();
                dataBar14SO.setLinkageFlag(true);
                dataBar14SO.setMode(Mode.OMNI);
                dataBar14SO.setSeparatorHeight(separatorHeight);
                linear = dataBar14SO;
                top_shift = 1;
                break;
            case DATABAR_14_STACK:
                DataBar14 dataBar14S = new DataBar14();
                dataBar14S.setLinkageFlag(true);
                dataBar14S.setMode(Mode.STACKED);
                dataBar14S.setSeparatorHeight(separatorHeight);
                linear = dataBar14S;
                top_shift = 1;
                break;
            case DATABAR_LIMITED:
                DataBarLimited dataBarLimited = new DataBarLimited();
                dataBarLimited.setLinkageFlag();
                linear = dataBarLimited;
                top_shift = 1;
                bottom_shift = (cc_mode == CompositeMode.CC_A ? 0 : 10); // left row address pattern omitted in 3-column CC-A
                break;
            case DATABAR_EXPANDED:
                DataBarExpanded dataBarExpanded = new DataBarExpanded();
                dataBarExpanded.setLinkageFlag(true);
                dataBarExpanded.setStacked(false);
                linear = dataBarExpanded;
                top_shift = 2;
                break;
            case DATABAR_EXPANDED_STACK:
                DataBarExpanded dataBarExpandedS = new DataBarExpanded();
                dataBarExpandedS.setLinkageFlag(true);
                dataBarExpandedS.setStacked(true);
                if (preferredColumns != null) {
                    dataBarExpandedS.setPreferredColumns(preferredColumns);
                }
                linear = dataBarExpandedS;
                top_shift = 2;
                break;
            default:
                throw new OkapiException("Linear symbol not recognised");
        }

        if (linearDataType != null) {
            linear.setDataType(linearDataType);
        }

        copyPropertiesTo(linear);
        linear.setContent(linearContent);

        if (symbology == LinearEncoding.CODE_128) {
            linearWidth = linear.symbol_width;
            if (cc_mode == CompositeMode.CC_C) {
                /* Width of composite component depends on width of linear component, so recalculate. */
                row_count = 0;
                rectangles.clear();
                symbol_height = 0;
                symbol_width = 0;
                encodeInfo.setLength(0);
                encodeComposite();
            } else {
                if (linearWidth > symbol_width) {
                    top_shift = (linearWidth - symbol_width) / 2;
                }
            }
        }

        for (Rectangle2D.Double orig : rectangles) {
            combine_rect.add(new Rectangle2D.Double(orig.x + top_shift, orig.y, orig.width, orig.height));
        }

        int extraSepHeight = separatorHeight - 1;
        if (extraSepHeight > 0 && (symbology == LinearEncoding.EAN || symbology == LinearEncoding.UPCA || symbology == LinearEncoding.UPCE)) {
            throw new OkapiException("Composite EAN and UPC separator height cannot be changed");
        }

        for (Rectangle2D.Double orig : linear.rectangles) {
            double h = orig.height + (extraSepHeight > 0 && orig.height == 1 ? extraSepHeight : 0);
            double y = orig.y +      (extraSepHeight > 0 && orig.height != 1 ? extraSepHeight : 0);
            combine_rect.add(new Rectangle2D.Double(orig.x + bottom_shift, y + symbol_height, orig.width, h));
        }

        for (TextBox orig : linear.texts) {
            combine_txt.add(new TextBox(orig.x + bottom_shift, orig.y + symbol_height + extraSepHeight, orig.width, orig.text, humanReadableAlignment));
        }

        int max_x = 0;
        for (Rectangle2D.Double rect : combine_rect) {
            if (rect.x + rect.width > max_x) {
                max_x = (int) Math.ceil(rect.x + rect.width);
            }
        }

        rectangles = combine_rect;
        texts = combine_txt;
        symbol_height += linear.symbol_height + extraSepHeight;
        symbol_width = max_x;
        info(linear.getEncodeInfo());
    }

    private void copyPropertiesTo(Symbol linear) {

        linear.setBarHeight(this.getBarHeight());
        linear.setHumanReadableLocation(this.getHumanReadableLocation());
        linear.setHumanReadableAlignment(this.getHumanReadableAlignment());
        linear.setModuleWidth(this.getModuleWidth());
        linear.setQuietZoneHorizontal(this.getQuietZoneHorizontal());
        linear.setQuietZoneVertical(this.getQuietZoneVertical());

        if (this.getFont() != null) {
            linear.setFont(this.getFont());
        } else {
            linear.setFontName(this.getFontName());
            linear.setFontSize(this.getFontSize());
        }
    }

    private void encodeComposite() {

        if (content.length() > 2990) {
            throw new OkapiException("2D component input data too long");
        }

        inputData = toBytes(content, StandardCharsets.US_ASCII);
        if (inputData == null) {
            throw new OkapiException("Invalid characters in input data");
        }

        cc_mode = userPreferredMode;

        if (cc_mode == CompositeMode.CC_C && symbology != LinearEncoding.CODE_128) {
            /* CC-C can only be used with a GS1-128 linear part */
            throw new OkapiException("Invalid mode (CC-C only valid with GS1-128 linear component)");
        }

        switch (symbology) {
            /* Determine width of 2D component according to ISO/IEC 24723 Table 1 */
            case EAN:
                if (eanCalculateVersion() == 8) {
                    cc_width = 3;
                } else {
                    cc_width = 4;
                }
                break;
            case UPCE:
            case DATABAR_14_STACK_OMNI:
            case DATABAR_14_STACK:
                cc_width = 2;
                break;
            case DATABAR_LIMITED:
                cc_width = 3;
                break;
            case CODE_128:
            case DATABAR_14:
            case DATABAR_EXPANDED:
            case UPCA:
            case DATABAR_EXPANDED_STACK:
                cc_width = 4;
                break;
        }

        infoLine("Composite Width: " + cc_width);

        if (cc_mode == CompositeMode.CC_A && !cc_binary_string()) {
            cc_mode = CompositeMode.CC_B;
        }

        if (cc_mode == CompositeMode.CC_B) { /* If the data didn't fit into CC-A it is recalculated for CC-B */
            if (!cc_binary_string()) {
                if (symbology != LinearEncoding.CODE_128) {
                    throw new OkapiException("Input too long");
                } else {
                    cc_mode = CompositeMode.CC_C;
                }
            }
        }

        if (cc_mode == CompositeMode.CC_C) {
            /* If the data didn't fit in CC-B (and linear
             * part is GS1-128) it is recalculated for CC-C */
            if (!cc_binary_string()) {
                throw new OkapiException("Input too long");
            }
        }

        switch (cc_mode) { /* Note that ecc_level is only relevant to CC-C */
            case CC_A:
                cc_a();
                infoLine("Composite Type: CC-A");
                break;
            case CC_B:
                cc_b();
                infoLine("Composite Type: CC-B");
                break;
            case CC_C:
                cc_c();
                infoLine("Composite Type: CC-C");
                break;
        }

        super.plotSymbol();
    }

    @Override
    protected void plotSymbol() {
        // empty
    }

    private int eanCalculateVersion() {
        /* Determine if EAN-8 or EAN-13 is being used */

        int length = 0;
        int i;
        boolean latch;

        latch = true;
        for (i = 0; i < linearContent.length(); i++) {
            if ((linearContent.charAt(i) >= '0') && (linearContent.charAt(i) <= '9')) {
                if (latch) {
                    length++;
                }
            } else {
                latch = false;
            }
        }

        if (length <= 7) {
            // EAN-8
            return 8;
        } else {
            // EAN-13
            return 13;
        }
    }

    private boolean calculateSymbolSize() {
        int i;
        int binary_length = binary_string.length();
        if (cc_mode == CompositeMode.CC_A) {
            /* CC-A 2D component - calculate remaining space */
            switch (cc_width) {
                case 2:
                    if (binary_length > 167) {
                        return false;
                    }
                    if (binary_length <= 167) {
                        target_bitsize = 167;
                    }
                    if (binary_length <= 138) {
                        target_bitsize = 138;
                    }
                    if (binary_length <= 118) {
                        target_bitsize = 118;
                    }
                    if (binary_length <= 108) {
                        target_bitsize = 108;
                    }
                    if (binary_length <= 88) {
                        target_bitsize = 88;
                    }
                    if (binary_length <= 78) {
                        target_bitsize = 78;
                    }
                    if (binary_length <= 59) {
                        target_bitsize = 59;
                    }
                    break;
                case 3:
                    if (binary_length > 167) {
                        return false;
                    }
                    if (binary_length <= 167) {
                        target_bitsize = 167;
                    }
                    if (binary_length <= 138) {
                        target_bitsize = 138;
                    }
                    if (binary_length <= 118) {
                        target_bitsize = 118;
                    }
                    if (binary_length <= 98) {
                        target_bitsize = 98;
                    }
                    if (binary_length <= 78) {
                        target_bitsize = 78;
                    }
                    break;
                case 4:
                    if (binary_length > 197) {
                        return false;
                    }
                    if (binary_length <= 197) {
                        target_bitsize = 197;
                    }
                    if (binary_length <= 167) {
                        target_bitsize = 167;
                    }
                    if (binary_length <= 138) {
                        target_bitsize = 138;
                    }
                    if (binary_length <= 108) {
                        target_bitsize = 108;
                    }
                    if (binary_length <= 78) {
                        target_bitsize = 78;
                    }
                    break;
            }
        }

        if (cc_mode == CompositeMode.CC_B) {
            /* CC-B 2D component - calculated from ISO/IEC 24728 Table 1  */
            switch (cc_width) {
                case 2:
                    if (binary_length > 336) {
                        return false;
                    }
                    if (binary_length <= 336) {
                        target_bitsize = 336;
                    }
                    if (binary_length <= 296) {
                        target_bitsize = 296;
                    }
                    if (binary_length <= 256) {
                        target_bitsize = 256;
                    }
                    if (binary_length <= 208) {
                        target_bitsize = 208;
                    }
                    if (binary_length <= 160) {
                        target_bitsize = 160;
                    }
                    if (binary_length <= 104) {
                        target_bitsize = 104;
                    }
                    if (binary_length <= 56) {
                        target_bitsize = 56;
                    }
                    break;
                case 3:
                    if (binary_length > 768) {
                        return false;
                    }
                    if (binary_length <= 768) {
                        target_bitsize = 768;
                    }
                    if (binary_length <= 648) {
                        target_bitsize = 648;
                    }
                    if (binary_length <= 536) {
                        target_bitsize = 536;
                    }
                    if (binary_length <= 416) {
                        target_bitsize = 416;
                    }
                    if (binary_length <= 304) {
                        target_bitsize = 304;
                    }
                    if (binary_length <= 208) {
                        target_bitsize = 208;
                    }
                    if (binary_length <= 152) {
                        target_bitsize = 152;
                    }
                    if (binary_length <= 112) {
                        target_bitsize = 112;
                    }
                    if (binary_length <= 72) {
                        target_bitsize = 72;
                    }
                    if (binary_length <= 32) {
                        target_bitsize = 32;
                    }
                    break;
                case 4:
                    if (binary_length > 1184) {
                        return false;
                    }
                    if (binary_length <= 1184) {
                        target_bitsize = 1184;
                    }
                    if (binary_length <= 1016) {
                        target_bitsize = 1016;
                    }
                    if (binary_length <= 840) {
                        target_bitsize = 840;
                    }
                    if (binary_length <= 672) {
                        target_bitsize = 672;
                    }
                    if (binary_length <= 496) {
                        target_bitsize = 496;
                    }
                    if (binary_length <= 352) {
                        target_bitsize = 352;
                    }
                    if (binary_length <= 264) {
                        target_bitsize = 264;
                    }
                    if (binary_length <= 208) {
                        target_bitsize = 208;
                    }
                    if (binary_length <= 152) {
                        target_bitsize = 152;
                    }
                    if (binary_length <= 96) {
                        target_bitsize = 96;
                    }
                    if (binary_length <= 56) {
                        target_bitsize = 56;
                    }
                    break;
            }
        }

        if (cc_mode == CompositeMode.CC_C) {
            /* CC-C 2D Component is a bit more complex! */
            int byte_length, codewords_used, ecc_level, ecc_codewords, rows;
            int codewords_total, target_codewords, target_bytesize;

            byte_length = binary_length / 8;
            if (binary_length % 8 != 0) {
                byte_length++;
            }

            codewords_used = (byte_length / 6) * 5;
            codewords_used += byte_length % 6;

            ecc_level = 7;
            if (codewords_used <= 1280) {
                ecc_level = 6;
            }
            if (codewords_used <= 640) {
                ecc_level = 5;
            }
            if (codewords_used <= 320) {
                ecc_level = 4;
            }
            if (codewords_used <= 160) {
                ecc_level = 3;
            }
            if (codewords_used <= 40) {
                ecc_level = 2;
            }
            ecc = ecc_level;
            ecc_codewords = 1;
            for (i = 1; i <= (ecc_level + 1); i++) {
                ecc_codewords *= 2;
            }

            codewords_used += ecc_codewords;
            codewords_used += 3;

            if (linearWidth == 0) {
                /* Linear component not yet calculated */
                cc_width = (int)(0.5 + Math.sqrt((codewords_used) / 3.0));
            } else {
                cc_width = (linearWidth - 53) / 17;
            }

            if ((codewords_used / cc_width) > 90) {
                /* stop the symbol from becoming too high */
                cc_width = cc_width + 1;
            }

            rows = codewords_used / cc_width;
            if (codewords_used % cc_width != 0) {
                rows++;
            }

            while (cc_width > (3 * rows)) {
                /* stop the symbol from becoming too wide (section 10) */
                cc_width--;

                rows = codewords_used / cc_width;
                if (codewords_used % cc_width != 0) {
                    rows++;
                }
            }

            codewords_total = cc_width * rows;

            target_codewords = codewords_total - ecc_codewords;
            target_codewords -= 3;

            target_bytesize = 6 * (target_codewords / 5);
            target_bytesize += target_codewords % 5;

            target_bitsize = 8 * target_bytesize;
        }

        remainder = target_bitsize - binary_length;
        return true;
    }

    private boolean cc_binary_string() {
        /* Handles all data encodation from section 5 of ISO/IEC 24723 */
        int encoding_method, read_posn, d1, d2, value, alpha_pad;
        int i, j, ai_crop, fnc1_latch;
        int group_val;
        int ai90_mode;
        boolean latch;
        int numeric_value, table3_letter;
        int latchOffset;

        encoding_method = 1;
        read_posn = 0;
        ai_crop = 0;
        fnc1_latch = 0;
        alpha_pad = 0;
        ai90_mode = 0;
        ecc = 0;
        value = 0;
        target_bitsize = 0;

        if (inputData[0] == '1' && (inputData[1] == '0' || inputData[1] == '1' || inputData[1] == '7') && inputData.length >= 8) {
            /* Source starts (10), (11) or (17) */
            encoding_method = 2;
        }

        if (inputData[0] == '9' && inputData[1] == '0') {
            /* Source starts (90) */
            encoding_method = 3;
        }

        info("Composite Encodation: ");
        switch (encoding_method) {
            case 1:
                infoLine("0");
                break;
            case 2:
                infoLine("10");
                break;
            case 3:
                infoLine("11");
                break;
        }

        binary_string = new StringBuilder();

        if (encoding_method == 1) {
            binary_string.append('0');
        }

        if (encoding_method == 2) {
            /* Encoding Method field "10" - date and lot number */

            binary_string.append("10");

            if (inputData[1] == '0') {
                /* No date data */
                binary_string.append("11");
            } else {
                /* Production Date (11) or Expiration Date (17) */
                group_val = ((10 * (inputData[2] - '0')) + (inputData[3] - '0')) * 384;
                group_val += (((10 * (inputData[4] - '0')) + (inputData[5] - '0')) - 1) * 32;
                group_val += (10 * (inputData[6] - '0')) + (inputData[7] - '0');
                binaryAppend(binary_string, group_val, 16);
                if (inputData[1] == '1') {
                    /* Production Date AI 11 */
                    binary_string.append('0');
                } else {
                    /* Expiration Date AI 17 */
                    binary_string.append('1');
                }
                read_posn = 8;
            }

            if((read_posn + 2) < inputData.length) {
                if ((inputData[read_posn] == '1') && (inputData[read_posn + 1] == '0')) {
                    /* Followed by AI 10 - strip this from general field */
                    read_posn += 2;
                } else {
                    /* An FNC1 character needs to be inserted in the general field */
                    fnc1_latch = 1;
                }
            } else {
                fnc1_latch = 1;
            }
        }

        if (encoding_method == 3) {
            /* Encodation Method field of "11" - AI 90 */
            /* "This encodation method may be used if an element string with an AI
             90 occurs at the start of the data message, and if the data field
             following the two-digit AI 90 starts with an alphanumeric string which
             complies with a specific format." (para 5.2.2) */

            j = inputData.length;
            for (i = inputData.length - 1; i > 2; i--) {
                if (inputData[i] == FNC1) {
                    j = i;
                }
            }

            int[] ninety = Arrays.copyOfRange(inputData, 2, j);

            /* Find out if the AI 90 data is alphabetic or numeric or both */

            int alpha = 0;
            int alphanum = 0;
            int numeric = 0;

            for (i = 0; i < ninety.length; i++) {

                if ((ninety[i] >= 'A') && (ninety[i] <= 'Z')) {
                    /* Character is alphabetic */
                    alpha += 1;
                }

                if ((ninety[i] >= '0') && (ninety[i] <= '9')) {
                    /* Character is numeric */
                    numeric += 1;
                }

                switch (ninety[i]) {
                    case '*':
                    case ',':
                    case '-':
                    case '.':
                    case '/':
                        alphanum += 1;
                        break;
                }

                if (!((ninety[i] >= '0' && ninety[i] <= '9') || (ninety[i] >= 'A' && ninety[i] <= 'Z'))) {
                    if (ninety[i] != '*' && ninety[i] != ',' && ninety[i] != '-' && ninety[i] != '.' && ninety[i] != '/') {
                        /* An Invalid AI 90 character */
                        throw new OkapiException("Invalid AI 90 data");
                    }
                }
            }

            /* must start with 0, 1, 2 or 3 digits followed by an uppercase character */
            int test1 = -1;
            for (i = 3; i >= 0; i--) {
                if (i < ninety.length && ninety[i] >= 'A' && ninety[i] <= 'Z') {
                    test1 = i; // first uppercase alpha position
                }
            }

            int test2 = 0;
            for (i = 0; i < test1; i++) {
                if (!(ninety[i] >= '0' && ninety[i] <= '9')) {
                    test2 = 1; // non-digit before first uppercase alpha
                }
            }

            /* leading zeros are not permitted */
            int test3 = 0;
            if (test1 >= 1 && ninety[0] == '0') {
                test3 = 1; // leading zero found
            }

            if (test1 != -1 && test2 == 0 && test3 == 0) {
                /* Encodation method "11" can be used */
                binary_string.append("11");

                numeric -= test1;
                alpha--;

                /* Decide on numeric, alpha or alphanumeric mode */
                /* Alpha mode is a special mode for AI 90 */

                if (alphanum > 0) {
                    /* Alphanumeric mode */
                    binary_string.append('0');
                    ai90_mode = 1;
                } else {
                    if (alpha > numeric) {
                        /* Alphabetic mode */
                        binary_string.append("11");
                        ai90_mode = 2;
                    } else {
                        /* Numeric mode */
                        binary_string.append("10");
                        ai90_mode = 3;
                    }
                }

                int next_ai_posn = 2 + ninety.length;

                if (next_ai_posn < inputData.length && inputData[next_ai_posn] == FNC1) {
                    /* There are more AIs afterwards */
                    if (next_ai_posn + 2 < inputData.length && inputData[next_ai_posn + 1] == '2' && inputData[next_ai_posn + 2] == '1') {
                        /* AI 21 follows */
                        ai_crop = 1;
                    }
                    if (next_ai_posn + 4 < inputData.length && inputData[next_ai_posn + 1] == '8' && inputData[next_ai_posn + 2] == '0' && inputData[next_ai_posn + 3] == '0' && inputData[next_ai_posn + 4] == '4') {
                        /* AI 8004 follows */
                        ai_crop = 2;
                    }
                }

                switch (ai_crop) {
                    case 0:
                        binary_string.append('0');
                        break;
                    case 1:
                        binary_string.append("10");
                        break;
                    case 2:
                        binary_string.append("11");
                        break;
                }

                numeric_value = 0;
                for (i = 0; i < test1; i++) {
                    numeric_value *= 10;
                    numeric_value += ninety[i] - '0';
                }

                table3_letter = -1;
                if (numeric_value < 31) {
                    table3_letter = "BDHIJKLNPQRSTVWZ".indexOf(ninety[test1]);
                }

                if (table3_letter != -1) {
                    /* Encoding can be done according to 5.2.2 c) 2) */
                    /* five bit binary string representing value before letter */
                    binaryAppend(binary_string, numeric_value, 5);
                    /* followed by four bit representation of letter from Table 3 */
                    binaryAppend(binary_string, table3_letter, 4);
                } else {
                    /* Encoding is done according to 5.2.2 c) 3) */
                    binary_string.append("11111");
                    /* ten bit representation of number */
                    binaryAppend(binary_string, numeric_value, 10);
                    /* five bit representation of ASCII character */
                    binaryAppend(binary_string, ninety[test1] - 65, 5);
                }

                read_posn = test1 + 3;

            } else {
                /* Use general field encodation instead */
                binary_string.append('0');
                read_posn = 0;
            }

            /* Now encode the rest of the AI 90 data field */
            if (ai90_mode == 2) {
                /* Alpha encodation (section 5.2.3) */
                do {
                    if (inputData[read_posn] >= '0' && inputData[read_posn] <= '9') {
                        binaryAppend(binary_string, inputData[read_posn] + 4, 6);
                    }
                    if (inputData[read_posn] >= 'A' && inputData[read_posn] <= 'Z') {
                        binaryAppend(binary_string, inputData[read_posn] - 65, 5);
                    }
                    if (inputData[read_posn] == FNC1) {
                        binary_string.append("11111");
                    }
                    read_posn++;
                } while (inputData[read_posn - 1] != FNC1 && read_posn < inputData.length);
                alpha_pad = 1; /* This is overwritten if a general field is encoded */
            }

            if (ai90_mode == 1) {
                /* Alphanumeric mode */
                do {
                    if (inputData[read_posn] >= '0' && inputData[read_posn] <= '9') {
                        binaryAppend(binary_string, inputData[read_posn] - 43, 5);
                    }
                    if (inputData[read_posn] >= 'A' && inputData[read_posn] <= 'Z') {
                        binaryAppend(binary_string, inputData[read_posn] - 33, 6);
                    }
                    switch (inputData[read_posn]) {
                        case FNC1:
                            binary_string.append("01111");
                            break;
                        case '*':
                            binary_string.append("111010");
                            break;
                        case ',':
                            binary_string.append("111011");
                            break;
                        case '-':
                            binary_string.append("111100");
                            break;
                        case '.':
                            binary_string.append("111101");
                            break;
                        case '/':
                            binary_string.append("111110");
                            break;
                    }
                    read_posn++;
                } while (inputData[read_posn - 1] != FNC1 && read_posn < inputData.length);
            }

            read_posn += (2 * ai_crop);

        } // end encodation method "11" (AI 90) handling

        /* The compressed data field has been processed if appropriate - the
         rest of the data (if any) goes into a general-purpose data compaction field */

        j = 0;
        if (fnc1_latch == 1) {
            /* Encodation method "10" has been used but it is not followed by
             AI 10, so a FNC1 character needs to be added */
            general_field = new int[inputData.length - read_posn + 1];
            general_field[0] = FNC1;
            System.arraycopy(inputData, read_posn, general_field, 1, general_field.length - 1);
        } else {
            general_field = Arrays.copyOfRange(inputData, read_posn, inputData.length);
        }

        latch = false;
        if (general_field.length != 0) {

            alpha_pad = 0;
            general_field_type = new GeneralFieldMode[general_field.length];

            for (i = 0; i < general_field.length; i++) {

                /* Table 13 - ISO/IEC 646 encodation */
                if (general_field[i] == FNC1) {
                    /* FNC1 can be encoded in any system */
                    general_field_type[i] = GeneralFieldMode.ANY_ENC;
                } else if (general_field[i] < ' ' || general_field[i] > 'z') {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                } else {
                    general_field_type[i] = GeneralFieldMode.ISOIEC;
                }

                if (general_field[i] == '#') {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                }
                if (general_field[i] == '$') {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                }
                if (general_field[i] == '@') {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                }
                if (general_field[i] == 92) {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                }
                if (general_field[i] == '^') {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                }
                if (general_field[i] == 96) {
                    general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
                    latch = true;
                }

                /* Table 12 - Alphanumeric encodation */
                if (general_field[i] >= 'A' && general_field[i] <= 'Z') {
                    general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
                }
                if (general_field[i] == '*') {
                    general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
                }
                if (general_field[i] == ',') {
                    general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
                }
                if (general_field[i] == '-') {
                    general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
                }
                if (general_field[i] == '.') {
                    general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
                }
                if (general_field[i] == '/') {
                    general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
                }

                /* Numeric encodation */
                if (general_field[i] >= '0' && general_field[i] <= '9') {
                    general_field_type[i] = GeneralFieldMode.ANY_ENC;
                }
            }

            if (latch) {
                /* Invalid characters in input data */
                throw new OkapiException("Invalid characters in input data");
            }

            for (i = 0; i < general_field.length - 1; i++) {
                if (general_field_type[i] == GeneralFieldMode.ISOIEC && general_field[i + 1] == FNC1) {
                    general_field_type[i + 1] = GeneralFieldMode.ISOIEC;
                }
            }

            for (i = 0; i < general_field.length - 1; i++) {
                if (general_field_type[i] == GeneralFieldMode.ALPHA_OR_ISO && general_field[i + 1] == FNC1) {
                    general_field_type[i + 1] = GeneralFieldMode.ALPHA_OR_ISO;
                }
            }

            latch = applyGeneralFieldRules();

            i = 0;
            do {
                switch (general_field_type[i]) {
                    case NUMERIC:
                        if (i != 0 && general_field_type[i - 1] != GeneralFieldMode.NUMERIC && general_field[i - 1] != FNC1) {
                            binary_string.append("000"); /* Numeric latch */
                        }
                        if (general_field[i] != FNC1) {
                            d1 = general_field[i] - '0';
                        } else {
                            d1 = 10;
                        }
                        if (i < general_field.length - 1) {
                            if (general_field[i + 1] != FNC1) {
                                d2 = general_field[i + 1] - '0';
                            } else {
                                d2 = 10;
                            }
                        } else {
                            d2 = 10;
                        }
                        if (d1 != 10 || d2 != 10) {
                            /* If (d1==10)&&(d2==10) then input is either FNC1,FNC1 or FNC1,EOL */
                            value = (11 * d1) + d2 + 8;
                            binaryAppend(binary_string, value, 7);
                            i += 2;
                        }
                        break;

                    case ALPHA:
                        if (i == 0 || general_field_type[i - 1] == GeneralFieldMode.NUMERIC || general_field[i - 1] == FNC1) {
                            binary_string.append("0000"); /* Alphanumeric latch */
                        } else if (general_field_type[i - 1] == GeneralFieldMode.ISOIEC) {
                            binary_string.append("00100"); /* ISO/IEC 646 latch */
                        }
                        if (general_field[i] >= '0' && general_field[i] <= '9') {
                            value = general_field[i] - 43;
                            binaryAppend(binary_string, value, 5);
                        }
                        if (general_field[i] >= 'A' && general_field[i] <= 'Z') {
                            value = general_field[i] - 33;
                            binaryAppend(binary_string, value, 6);
                        }
                        if (general_field[i] == FNC1) {
                            binary_string.append("01111"); /* FNC1/Numeric latch */
                        }
                        if (general_field[i] == '*') {
                            binary_string.append("111010"); /* asterisk */
                        }
                        if (general_field[i] == ',') {
                            binary_string.append("111011"); /* comma */
                        }
                        if (general_field[i] == '-') {
                            binary_string.append("111100"); /* minus or hyphen */
                        }
                        if (general_field[i] == '.') {
                            binary_string.append("111101"); /* period or full stop */
                        }
                        if (general_field[i] == '/') {
                            binary_string.append("111110"); /* slash or solidus */
                        }
                        i++;
                        break;

                    case ISOIEC:
                        if (i == 0 || general_field_type[i - 1] == GeneralFieldMode.NUMERIC || general_field[i - 1] == FNC1) {
                            binary_string.append("0000"); /* Alphanumeric latch */
                            binary_string.append("00100"); /* ISO/IEC 646 latch */
                        } else if (general_field_type[i - 1] == GeneralFieldMode.ALPHA) {
                            binary_string.append("00100"); /* ISO/IEC 646 latch */
                        }
                        if (general_field[i] >= '0' && general_field[i] <= '9') {
                            value = general_field[i] - 43;
                            binaryAppend(binary_string, value, 5);
                        }
                        if (general_field[i] >= 'A' && general_field[i] <= 'Z') {
                            value = general_field[i] - 1;
                            binaryAppend(binary_string, value, 7);
                        }
                        if (general_field[i] >= 'a' && general_field[i] <= 'z') {
                            value = general_field[i] - 7;
                            binaryAppend(binary_string, value, 7);
                        }
                        if (general_field[i] == FNC1) {
                            binary_string.append("01111"); /* FNC1/Numeric latch */
                        }
                        if (general_field[i] == '!') {
                            binary_string.append("11101000"); /* exclamation mark */
                        }
                        if (general_field[i] == 34) {
                            binary_string.append("11101001"); /* quotation mark */
                        }
                        if (general_field[i] == 37) {
                            binary_string.append("11101010"); /* percent sign */
                        }
                        if (general_field[i] == '&') {
                            binary_string.append("11101011"); /* ampersand */
                        }
                        if (general_field[i] == 39) {
                            binary_string.append("11101100"); /* apostrophe */
                        }
                        if (general_field[i] == '(') {
                            binary_string.append("11101101"); /* left parenthesis */
                        }
                        if (general_field[i] == ')') {
                            binary_string.append("11101110"); /* right parenthesis */
                        }
                        if (general_field[i] == '*') {
                            binary_string.append("11101111"); /* asterisk */
                        }
                        if (general_field[i] == '+') {
                            binary_string.append("11110000"); /* plus sign */
                        }
                        if (general_field[i] == ',') {
                            binary_string.append("11110001"); /* comma */
                        }
                        if (general_field[i] == '-') {
                            binary_string.append("11110010"); /* minus or hyphen */
                        }
                        if (general_field[i] == '.') {
                            binary_string.append("11110011"); /* period or full stop */
                        }
                        if (general_field[i] == '/') {
                            binary_string.append("11110100"); /* slash or solidus */
                        }
                        if (general_field[i] == ':') {
                            binary_string.append("11110101"); /* colon */
                        }
                        if (general_field[i] == ';') {
                            binary_string.append("11110110"); /* semicolon */
                        }
                        if (general_field[i] == '<') {
                            binary_string.append("11110111"); /* less-than sign */
                        }
                        if (general_field[i] == '=') {
                            binary_string.append("11111000"); /* equals sign */
                        }
                        if (general_field[i] == '>') {
                            binary_string.append("11111001"); /* greater-than sign */
                        }
                        if (general_field[i] == '?') {
                            binary_string.append("11111010"); /* question mark */
                        }
                        if (general_field[i] == '_') {
                            binary_string.append("11111011"); /* underline or low line */
                        }
                        if (general_field[i] == ' ') {
                            binary_string.append("11111100"); /* space */
                        }
                        i++;
                        break;
                }

                latchOffset = 0;
                if (latch) {
                    latchOffset = 1;
                }

            } while (i + latchOffset < general_field.length);

        } // end general-purpose compaction data handling

        if (!calculateSymbolSize()) {
            return false;
        }

        if (latch) {
            /* There is still one more numeric digit to encode */
            i = general_field.length - 1;
            if (general_field[i] == FNC1) {
                binary_string.append("000001111");
            } else {
                if (remainder >= 4 && remainder <= 6) {
                    /* ISO/IEC 24723:2010 5.4.1 c) 2) "If four to six bits remain, add 1 to the digit value and encode the result in the next four bits" */
                    d1 = general_field[i] - '0';
                    value = d1 + 1;
                    binaryAppend(binary_string, value, 4);
                } else {
                    d1 = general_field[i] - '0';
                    d2 = 10;
                    value = (11 * d1) + d2 + 8;
                    binaryAppend(binary_string, value, 7);
                    /* This may push the symbol up to the next size */
                }
            }
        }

        if (binary_string.length() > 11805) { /* (2361 * 5) */
            throw new OkapiException("Input too long");
        }

        /* size of the symbol may have changed when adding data in the above sequence */
        if (!calculateSymbolSize()) {
            return false;
        }

        infoLine("Composite Binary Length: " + binary_string.length());
        logBinaryString();

        if (binary_string.length() < target_bitsize) {
            /* Now add padding to binary string */
            if (alpha_pad == 1) {
                /* Extra FNC1 character required after Alpha encodation (section 5.2.3) */
                binary_string.append("11111");
                alpha_pad = 0;
            }
            if (general_field.length == 0 || general_field_type[general_field.length - 1] == GeneralFieldMode.NUMERIC) {
                /* Latch from numeric to alphanumeric */
                binary_string.append("0000");
            }
            while (binary_string.length() < target_bitsize) {
                /* Latch back and forth between alphanumeric and ISO/IEC encodation (section 5.3.4) */
                binary_string.append("00100");
            }
            binary_string.delete(target_bitsize, binary_string.length());
        }

        return true;
    }

    private void logBinaryString() {
        int i, nibble;
        /* Display binary string as hexadecimal */

        info("Composite Binary String: ");
        nibble = 0;
        for(i = 0; i < binary_string.length(); i++) {
            switch (i % 4) {
                case 0:
                    if (binary_string.charAt(i) == '1') {
                        nibble += 8;
                    }
                    break;
                case 1:
                    if (binary_string.charAt(i) == '1') {
                        nibble += 4;
                    }
                    break;
                case 2:
                    if (binary_string.charAt(i) == '1') {
                        nibble += 2;
                    }
                    break;
                case 3:
                    if (binary_string.charAt(i) == '1') {
                        nibble += 1;
                    }
                    info(Integer.toHexString(nibble));
                    nibble = 0;
                    break;
            }
        }

        if ((binary_string.length() % 4) != 0) {
            info(Integer.toHexString(nibble));
        }
        infoLine();
    }

    private boolean applyGeneralFieldRules() {
        /* Attempts to apply encoding rules from sections 7.2.5.5.1 to 7.2.5.5.3
         of ISO/IEC 24724:2006 */

        int block_count, i, j, k;
        GeneralFieldMode current, next, last;
        int[] blockLength = new int[200];
        GeneralFieldMode[] blockType = new GeneralFieldMode[200];

        block_count = 0;

        blockLength[block_count] = 1;
        blockType[block_count] = general_field_type[0];

        for (i = 1; i < general_field.length; i++) {
            current = general_field_type[i];
            last = general_field_type[i - 1];

            if (current == last) {
                blockLength[block_count] = blockLength[block_count] + 1;
            } else {
                block_count++;
                blockLength[block_count] = 1;
                blockType[block_count] = general_field_type[i];
            }
        }

        block_count++;

        for (i = 0; i < block_count; i++) {
            current = blockType[i];
            next = blockType[i + 1];

            if ((current == GeneralFieldMode.ISOIEC) && (i != (block_count - 1))) {
                if ((next == GeneralFieldMode.ANY_ENC) && (blockLength[i + 1] >= 4)) {
                    blockType[i + 1] = GeneralFieldMode.NUMERIC;
                }
                if ((next == GeneralFieldMode.ANY_ENC) && (blockLength[i + 1] < 4)) {
                    blockType[i + 1] = GeneralFieldMode.ISOIEC;
                }
                if ((next == GeneralFieldMode.ALPHA_OR_ISO) && (blockLength[i + 1] >= 5)) {
                    blockType[i + 1] = GeneralFieldMode.ALPHA;
                }
                if ((next == GeneralFieldMode.ALPHA_OR_ISO) && (blockLength[i + 1] < 5)) {
                    blockType[i + 1] = GeneralFieldMode.ISOIEC;
                }
            }

            if (current == GeneralFieldMode.ALPHA_OR_ISO) {
                blockType[i] = GeneralFieldMode.ALPHA;
            }

            if ((current == GeneralFieldMode.ALPHA) && (i != (block_count - 1))) {
                if ((next == GeneralFieldMode.ANY_ENC) && (blockLength[i + 1] >= 6)) {
                    blockType[i + 1] = GeneralFieldMode.NUMERIC;
                }
                if ((next == GeneralFieldMode.ANY_ENC) && (blockLength[i + 1] < 6)) {
                    if ((i == block_count - 2) && (blockLength[i + 1] >= 4)) {
                        blockType[i + 1] = GeneralFieldMode.NUMERIC;
                    } else {
                        blockType[i + 1] = GeneralFieldMode.ALPHA;
                    }
                }
            }

            if (current == GeneralFieldMode.ANY_ENC) {
                blockType[i] = GeneralFieldMode.NUMERIC;
            }
        }

        if (block_count > 1) {
            i = 1;
            while (i < block_count) {
                if (blockType[i - 1] == blockType[i]) {
                    /* bring together */
                    blockLength[i - 1] = blockLength[i - 1] + blockLength[i];
                    j = i + 1;

                    /* decrease the list */
                    while (j < block_count) {
                        blockLength[j - 1] = blockLength[j];
                        blockType[j - 1] = blockType[j];
                        j++;
                    }
                    block_count--;
                    i--;
                }
                i++;
            }
        }

        for (i = 0; i < block_count - 1; i++) {
            if ((blockType[i] == GeneralFieldMode.NUMERIC) && ((blockLength[i] & 1) != 0)) {
                /* Odd size numeric block */
                blockLength[i] = blockLength[i] - 1;
                blockLength[i + 1] = blockLength[i + 1] + 1;
            }
        }

        j = 0;
        for (i = 0; i < block_count; i++) {
            for (k = 0; k < blockLength[i]; k++) {
                general_field_type[j] = blockType[i];
                j++;
            }
        }

        if ((blockType[block_count - 1] == GeneralFieldMode.NUMERIC) && ((blockLength[block_count - 1] & 1) != 0)) {
            /* If the last block is numeric and an odd size, further
             processing needs to be done outside this procedure */
            return true;
        } else {
            return false;
        }
    }

    private void cc_a() {
        /* CC-A 2D component */
        int i, strpos, segment, cwCnt, variant, rows;
        int k, offset, j, total;
        int[] rsCodeWords = new int[8];
        int LeftRAPStart, RightRAPStart, CentreRAPStart, StartCluster;
        int LeftRAP, RightRAP, CentreRAP, Cluster;
        int[] dummy = new int[5];
        int flip, loop;
        String codebarre;
        StringBuilder bin = new StringBuilder();
        StringBuilder local_source; /* A copy of source but with padding zeroes to make 208 bits */

        variant = 0;

        for (i = 0; i < 13; i++) {
            bitStr[i] = 0;
        }
        for (i = 0; i < 28; i++) {
            codeWords[i] = 0;
        }

        local_source = new StringBuilder(208);
        local_source.append(binary_string);
        for (i = binary_string.length(); i < 208; i++) {
            local_source.append('0');
        }

        for (segment = 0; segment < 13; segment++) {
            strpos = segment * 16;
            bitStr[segment] = 0;
            for (i = 0; i < 16; i++) {
                if (local_source.charAt(strpos + i) == '1') {
                    bitStr[segment] += 0x8000 >> i;
                }
            }
        }

        init928();
        /* encode codeWords from bitStr */
        cwCnt = encode928(binary_string.length());

        switch (cc_width) {
            case 2:
                switch (cwCnt) {
                    case 6:
                        variant = 0;
                        break;
                    case 8:
                        variant = 1;
                        break;
                    case 9:
                        variant = 2;
                        break;
                    case 11:
                        variant = 3;
                        break;
                    case 12:
                        variant = 4;
                        break;
                    case 14:
                        variant = 5;
                        break;
                    case 17:
                        variant = 6;
                        break;
                }
                break;
            case 3:
                switch (cwCnt) {
                    case 8:
                        variant = 7;
                        break;
                    case 10:
                        variant = 8;
                        break;
                    case 12:
                        variant = 9;
                        break;
                    case 14:
                        variant = 10;
                        break;
                    case 17:
                        variant = 11;
                        break;
                }
                break;
            case 4:
                switch (cwCnt) {
                    case 8:
                        variant = 12;
                        break;
                    case 11:
                        variant = 13;
                        break;
                    case 14:
                        variant = 14;
                        break;
                    case 17:
                        variant = 15;
                        break;
                    case 20:
                        variant = 16;
                        break;
                }
                break;
        }

        rows = CCA_VARIANTS[variant];
        k = CCA_VARIANTS[17 + variant];
        offset = CCA_VARIANTS[34 + variant];

        /* Reed-Solomon error correction */

        for (i = 0; i < 8; i++) {
            rsCodeWords[i] = 0;
        }
        total = 0;
        info("Composite Codewords: ");
        for (i = 0; i < cwCnt; i++) {
            total = (codeWords[i] + rsCodeWords[k - 1]) % 929;
            for (j = k - 1; j >= 0; j--) {
                if (j == 0) {
                    rsCodeWords[j] = (929 - (total * CCA_COEFFS[offset + j]) % 929) % 929;
                } else {
                    rsCodeWords[j] = (rsCodeWords[j - 1] + 929 - (total * CCA_COEFFS[offset + j]) % 929) % 929;
                }
            }
            infoSpace(codeWords[i]);
        }
        infoLine();

        for (j = 0; j < k; j++) {
            if (rsCodeWords[j] != 0) {
                rsCodeWords[j] = 929 - rsCodeWords[j];
            }
        }

        for (i = k - 1; i >= 0; i--) {
            codeWords[cwCnt] = rsCodeWords[i];
            cwCnt++;
        }

        /* Place data into table */
        LeftRAPStart = A_RAP_TABLE[variant];
        CentreRAPStart = A_RAP_TABLE[variant + 17];
        RightRAPStart = A_RAP_TABLE[variant + 34];
        StartCluster = A_RAP_TABLE[variant + 51] / 3;

        LeftRAP = LeftRAPStart;
        CentreRAP = CentreRAPStart;
        RightRAP = RightRAPStart;
        Cluster = StartCluster; /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */

        readable = "";
        row_count = rows;
        pattern = new String[row_count];
        row_height = new int[row_count];

        for (i = 0; i < rows; i++) {
            codebarre = "";
            offset = 929 * Cluster;
            for (j = 0; j < 5; j++) {
                dummy[j] = 0;
            }
            for (j = 0; j < cc_width; j++) {
                dummy[j + 1] = codeWords[i * cc_width + j];
            }
            /* Copy the data into codebarre */
            if (cc_width != 3) {
                codebarre += RAPLR[LeftRAP];
            }
            codebarre += "1";
            codebarre += CODAGEMC[offset + dummy[1]];
            codebarre += "1";
            if (cc_width == 3) {
                codebarre += RAPC[CentreRAP];
            }
            if (cc_width >= 2) {
                codebarre += "1";
                codebarre += CODAGEMC[offset + dummy[2]];
                codebarre += "1";
            }
            if (cc_width == 4) {
                codebarre += RAPC[CentreRAP];
            }
            if (cc_width >= 3) {
                codebarre += "1";
                codebarre += CODAGEMC[offset + dummy[3]];
                codebarre += "1";
            }
            if (cc_width == 4) {
                codebarre += "1";
                codebarre += CODAGEMC[offset + dummy[4]];
                codebarre += "1";
            }
            codebarre += RAPLR[RightRAP];
            codebarre += "1"; /* stop */

            /* Now codebarre is a mixture of letters and numbers */

            flip = 1;
            bin.setLength(0);
            for (loop = 0; loop < codebarre.length(); loop++) {
                if ((codebarre.charAt(loop) >= '0') && (codebarre.charAt(loop) <= '9')) {
                    for (k = 0; k < codebarre.charAt(loop) - '0'; k++) {
                        if (flip == 0) {
                            bin.append('0');
                        } else {
                            bin.append('1');
                        }
                    }
                    if (flip == 0) {
                        flip = 1;
                    } else {
                        flip = 0;
                    }
                } else {
                    bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]);
                }
            }

            row_height[i] = 2;
            pattern[i] = bin2pat(bin);

            /* Set up RAPs and Cluster for next row */
            LeftRAP++;
            CentreRAP++;
            RightRAP++;
            Cluster++;

            if (LeftRAP == 53) {
                LeftRAP = 1;
            }
            if (CentreRAP == 53) {
                CentreRAP = 1;
            }
            if (RightRAP == 53) {
                RightRAP = 1;
            }
            if (Cluster == 3) {
                Cluster = 0;
            }
        }
    }

    /* initialize pwr928 encoding table */
    private void init928() {
        int i, j, v;
        int[] cw = new int[7];
        cw[6] = 1;
        for (i = 5; i >= 0; i--) {
            cw[i] = 0;
        }

        for (i = 0; i < 7; i++) {
            pwr928[0][i] = cw[i];
        }
        for (j = 1; j < 69; j++) {
            for (v = 0, i = 6; i >= 1; i--) {
                v = (2 * cw[i]) + (v / 928);
                pwr928[j][i] = cw[i] = v % 928;
            }
            pwr928[j][0] = cw[0] = (2 * cw[0]) + (v / 928);
        }
    }

    /* converts bit string to base 928 values, codeWords[0] is highest order */
    private int encode928(int bitLng) {
        int i, j, b, bitCnt, cwNdx, cwCnt, cwLng;
        for (cwNdx = cwLng = b = 0; b < bitLng; b += 69, cwNdx += 7) {
            bitCnt = min(bitLng - b, 69);
            cwLng += cwCnt = bitCnt / 10 + 1;
            for (i = 0; i < cwCnt; i++) {
                codeWords[cwNdx + i] = 0; /* init 0 */
            }
            for (i = 0; i < bitCnt; i++) {
                if (getBit(b + bitCnt - i - 1)) {
                    for (j = 0; j < cwCnt; j++) {
                        codeWords[cwNdx + j] += pwr928[i][j + 7 - cwCnt];
                    }
                }
            }
            for (i = cwCnt - 1; i > 0; i--) {
                /* add "carries" */
                codeWords[cwNdx + i - 1] += codeWords[cwNdx + i] / 928;
                codeWords[cwNdx + i] %= 928;
            }
        }
        return (cwLng);
    }

    private int min(int first, int second) {
        if (first <= second) {
            return first;
        } else {
            return second;
        }
    }

    /* gets bit in bitString at bitPos */
    private boolean getBit(int arg) {
        if ((bitStr[arg >> 4] & (0x8000 >> (arg & 15))) != 0) {
            return true;
        } else {
            return false;
        }
    }

    private void cc_b() {
        /* CC-B 2D component */
        int length, i, binloc;
        int k, j, longueur, offset;
        int[] mccorrection = new int[50];
        int total;
        int[] dummy = new int[5];
        String codebarre;
        StringBuilder bin = new StringBuilder();
        int variant, LeftRAPStart, CentreRAPStart, RightRAPStart, StartCluster;
        int LeftRAP, CentreRAP, RightRAP, Cluster, flip, loop;
        int option_2, rows;
        inputData = new int[(binary_string.length() / 8) + 3];

        length = binary_string.length() / 8;

        for (i = 0; i < length; i++) {
            binloc = i * 8;

            inputData[i] = 0;
            for (j = 0; j < 8; j++) {
                if (binary_string.charAt(binloc + j) == '1') {
                    inputData[i] += 0x80 >> j;
                }
            }
        }

        codeWordCount = 0;

        /* "the CC-B component shall have codeword 920 in the first symbol character position" (section 9a) */
        codeWords[codeWordCount] = 920;
        codeWordCount++;

        byteprocess(0, length);

        /* Now figure out which variant of the symbol to use and load values accordingly */

        variant = 0;

        if (cc_width == 2) {
            variant = 13;
            if (codeWordCount <= 33) {
                variant = 12;
            }
            if (codeWordCount <= 29) {
                variant = 11;
            }
            if (codeWordCount <= 24) {
                variant = 10;
            }
            if (codeWordCount <= 19) {
                variant = 9;
            }
            if (codeWordCount <= 13) {
                variant = 8;
            }
            if (codeWordCount <= 8) {
                variant = 7;
            }
        }

        if (cc_width == 3) {
            variant = 23;
            if (codeWordCount <= 70) {
                variant = 22;
            }
            if (codeWordCount <= 58) {
                variant = 21;
            }
            if (codeWordCount <= 46) {
                variant = 20;
            }
            if (codeWordCount <= 34) {
                variant = 19;
            }
            if (codeWordCount <= 24) {
                variant = 18;
            }
            if (codeWordCount <= 18) {
                variant = 17;
            }
            if (codeWordCount <= 14) {
                variant = 16;
            }
            if (codeWordCount <= 10) {
                variant = 15;
            }
            if (codeWordCount <= 6) {
                variant = 14;
            }
        }

        if (cc_width == 4) {
            variant = 34;
            if (codeWordCount <= 108) {
                variant = 33;
            }
            if (codeWordCount <= 90) {
                variant = 32;
            }
            if (codeWordCount <= 72) {
                variant = 31;
            }
            if (codeWordCount <= 54) {
                variant = 30;
            }
            if (codeWordCount <= 39) {
                variant = 29;
            }
            if (codeWordCount <= 30) {
                variant = 28;
            }
            if (codeWordCount <= 24) {
                variant = 27;
            }
            if (codeWordCount <= 18) {
                variant = 26;
            }
            if (codeWordCount <= 12) {
                variant = 25;
            }
            if (codeWordCount <= 8) {
                variant = 24;
            }
        }

        /* Now we have the variant we can load the data - from here on the same as MicroPDF417 code */
        variant--;
        option_2 = MICRO_VARIANTS[variant]; /* columns */
        rows = MICRO_VARIANTS[variant + 34]; /* rows */
        k = MICRO_VARIANTS[variant + 68]; /* number of EC CWs */
        longueur = (option_2 * rows) - k; /* number of non-EC CWs */
        i = longueur - codeWordCount; /* amount of padding required */
        offset = MICRO_VARIANTS[variant + 102]; /* coefficient offset */

        /* We add the padding */
        while (i > 0) {
            codeWords[codeWordCount] = 900;
            codeWordCount++;
            i--;
        }

        /* Reed-Solomon error correction */
        longueur = codeWordCount;
        for (loop = 0; loop < 50; loop++) {
            mccorrection[loop] = 0;
        }
        total = 0;
        info("Composite Codewords: ");
        for (i = 0; i < longueur; i++) {
            total = (codeWords[i] + mccorrection[k - 1]) % 929;
            for (j = k - 1; j >= 0; j--) {
                if (j == 0) {
                    mccorrection[j] = (929 - (total * MICROCOEFFS[offset + j]) % 929) % 929;
                } else {
                    mccorrection[j] = (mccorrection[j - 1] + 929 - (total * MICROCOEFFS[offset + j]) % 929) % 929;
                }
            }
            infoSpace(codeWords[i]);
        }
        infoLine();

        for (j = 0; j < k; j++) {
            if (mccorrection[j] != 0) {
                mccorrection[j] = 929 - mccorrection[j];
            }
        }
        /* we add these codes to the string */
        for (i = k - 1; i >= 0; i--) {
            codeWords[codeWordCount] = mccorrection[i];
            codeWordCount++;
        }

        /* Now get the RAP (Row Address Pattern) start values */
        LeftRAPStart = RAP_TABLE[variant];
        CentreRAPStart = RAP_TABLE[variant + 34];
        RightRAPStart = RAP_TABLE[variant + 68];
        StartCluster = RAP_TABLE[variant + 102] / 3;

        /* That's all values loaded, get on with the encoding */

        LeftRAP = LeftRAPStart;
        CentreRAP = CentreRAPStart;
        RightRAP = RightRAPStart;
        Cluster = StartCluster; /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */

        readable = "";
        row_count = rows;
        pattern = new String[row_count];
        row_height = new int[row_count];

        for (i = 0; i < rows; i++) {
            codebarre = "";
            offset = 929 * Cluster;
            for (j = 0; j < 5; j++) {
                dummy[j] = 0;
            }
            for (j = 0; j < option_2; j++) {
                dummy[j + 1] = codeWords[i * option_2 + j];
            }
            /* Copy the data into codebarre */
            codebarre += RAPLR[LeftRAP];
            codebarre += "1";
            codebarre += CODAGEMC[offset + dummy[1]];
            codebarre += "1";
            if (cc_width == 3) {
                codebarre += RAPC[CentreRAP];
            }
            if (cc_width >= 2) {
                codebarre += "1";
                codebarre += CODAGEMC[offset + dummy[2]];
                codebarre += "1";
            }
            if (cc_width == 4) {
                codebarre += RAPC[CentreRAP];
            }
            if (cc_width >= 3) {
                codebarre += "1";
                codebarre += CODAGEMC[offset + dummy[3]];
                codebarre += "1";
            }
            if (cc_width == 4) {
                codebarre += "1";
                codebarre += CODAGEMC[offset + dummy[4]];
                codebarre += "1";
            }
            codebarre += RAPLR[RightRAP];
            codebarre += "1"; /* stop */

            /* Now codebarre is a mixture of letters and numbers */

            flip = 1;
            bin.setLength(0);
            for (loop = 0; loop < codebarre.length(); loop++) {
                if ((codebarre.charAt(loop) >= '0') && (codebarre.charAt(loop) <= '9')) {
                    for (k = 0; k < codebarre.charAt(loop) - '0'; k++) {
                        if (flip == 0) {
                            bin.append('0');
                        } else {
                            bin.append('1');
                        }
                    }
                    if (flip == 0) {
                        flip = 1;
                    } else {
                        flip = 0;
                    }
                } else {
                    bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]);
                }
            }

            pattern[i] = bin2pat(bin);
            row_height[i] = 2;

            /* Set up RAPs and Cluster for next row */
            LeftRAP++;
            CentreRAP++;
            RightRAP++;
            Cluster++;

            if (LeftRAP == 53) {
                LeftRAP = 1;
            }
            if (CentreRAP == 53) {
                CentreRAP = 1;
            }
            if (RightRAP == 53) {
                RightRAP = 1;
            }
            if (Cluster == 3) {
                Cluster = 0;
            }

        }
    }

    private void cc_c() {
        /* CC-C 2D component - byte compressed PDF417 */
        int length, i, binloc, k;
        int offset, longueur, loop, total, j;
        int[] mccorrection = new int[520];
        int c1, c2, c3;
        int[] dummy = new int[35];
        String codebarre;
        StringBuilder bin = new StringBuilder();
        inputData = new int[(binary_string.length() / 8) + 4];

        length = binary_string.length() / 8;

        for (i = 0; i < length; i++) {
            binloc = i * 8;
            inputData[i] = 0;
            for (j = 0; j < 8; j++) {
                if (binary_string.charAt(binloc + j) == '1') {
                    inputData[i] += 0x80 >> j;
                }
            }
        }

        codeWordCount = 0;
        codeWords[codeWordCount] = 0; /* space for length descriptor */
        codeWordCount++;
        codeWords[codeWordCount] = 920; /* CC-C identifier */
        codeWordCount++;

        byteprocess(0, length);

        codeWords[0] = codeWordCount;

        k = 1;
        for (i = 1; i <= (ecc + 1); i++) {
            k *= 2;
        }

        /* 796 - we now take care of the Reed Solomon codes */
        switch (ecc) {
            case 1:
                offset = 2;
                break;
            case 2:
                offset = 6;
                break;
            case 3:
                offset = 14;
                break;
            case 4:
                offset = 30;
                break;
            case 5:
                offset = 62;
                break;
            case 6:
                offset = 126;
                break;
            case 7:
                offset = 254;
                break;
            case 8:
                offset = 510;
                break;
            default:
                offset = 0;
                break;
        }

        longueur = codeWordCount;
        for (loop = 0; loop < 520; loop++) {
            mccorrection[loop] = 0;
        }
        total = 0;
        info("Composite Codewords: ");
        for (i = 0; i < longueur; i++) {
            total = (codeWords[i] + mccorrection[k - 1]) % 929;
            for (j = k - 1; j >= 0; j--) {
                if (j == 0) {
                    mccorrection[j] = (929 - (total * COEFRS[offset + j]) % 929) % 929;
                } else {
                    mccorrection[j] = (mccorrection[j - 1] + 929 - (total * COEFRS[offset + j]) % 929) % 929;
                }
            }
            infoSpace(codeWords[i]);
        }
        infoLine();

        for (j = 0; j < k; j++) {
            if (mccorrection[j] != 0) {
                mccorrection[j] = 929 - mccorrection[j];
            }
        }
        /* we add these codes to the string */
        for (i = k - 1; i >= 0; i--) {
            codeWords[codeWordCount] = mccorrection[i];
            codeWordCount++;
        }

        /* 818 - The CW string is finished */
        c1 = (codeWordCount / cc_width - 1) / 3;
        c2 = ecc * 3 + (codeWordCount / cc_width - 1) % 3;
        c3 = cc_width - 1;

        readable = "";
        row_count = codeWordCount / cc_width;
        pattern = new String[row_count];
        row_height = new int[row_count];

        /* we now encode each row */
        for (i = 0; i <= (codeWordCount / cc_width) - 1; i++) {
            for (j = 0; j < cc_width; j++) {
                dummy[j + 1] = codeWords[i * cc_width + j];
            }
            k = (i / 3) * 30;
            switch (i % 3) {
                /* follows this pattern from US Patent 5,243,655:
                 Row 0: L0 (row #, # of rows)         R0 (row #, # of columns)
                 Row 1: L1 (row #, security level)    R1 (row #, # of rows)
                 Row 2: L2 (row #, # of columns)      R2 (row #, security level)
                 Row 3: L3 (row #, # of rows)         R3 (row #, # of columns)
                 etc. */
                case 0:
                    dummy[0] = k + c1;
                    dummy[cc_width + 1] = k + c3;
                    break;
                case 1:
                    dummy[0] = k + c2;
                    dummy[cc_width + 1] = k + c1;
                    break;
                case 2:
                    dummy[0] = k + c3;
                    dummy[cc_width + 1] = k + c2;
                    break;
            }
            codebarre = "+*"; /* Start with a start char and a separator */

            for (j = 0; j <= cc_width + 1; j++) {
                switch (i % 3) {
                    case 1:
                        offset = 929; /* cluster(3) */ break;
                    case 2:
                        offset = 1858; /* cluster(6) */ break;
                    default:
                        offset = 0; /* cluster(0) */ break;
                }
                codebarre += CODAGEMC[offset + dummy[j]];
                codebarre += "*";
            }
            codebarre += "-";

            bin.setLength(0);
            for (loop = 0; loop < codebarre.length(); loop++) {
                bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]);
            }
            pattern[i] = bin2pat(bin);
            row_height[i] = 3;
        }
    }

    private void byteprocess(int start, int length) {
        int len = 0;
        int chunkLen = 0;
        BigInteger mantisa;
        BigInteger total;
        BigInteger word;

        /* select the switch for multiple of 6 bytes */
        if ((binary_string.length() % 6) == 0) {
            codeWords[codeWordCount++] = 924;
        } else {
            codeWords[codeWordCount++] = 901;
        }

        while (len < length) {
            chunkLen = length - len;
            if (6 <= chunkLen) /* Take groups of 6 */ {
                chunkLen = 6;
                len += chunkLen;
                total = BigInteger.valueOf(0);

                while ((chunkLen--) != 0) {
                    mantisa = BigInteger.valueOf(inputData[start++]);
                    total = total.or(mantisa.shiftLeft(chunkLen * 8));
                }

                chunkLen = 5;

                while ((chunkLen--) != 0) {

                    word = total.mod(BigInteger.valueOf(900));
                    codeWords[codeWordCount + chunkLen] = word.intValue();
                    total = total.divide(BigInteger.valueOf(900));
                }
                codeWordCount += 5;
            } else /*  If it remain a group of less than 6 bytes   */ {
                len += chunkLen;
                while ((chunkLen--) != 0) {
                    codeWords[codeWordCount++] = inputData[start++];
                }
            }
        }
    }
} public class DataBar14 extends Symbol {

    public enum Mode {
        /** DataBar-14 */
        LINEAR,
        /** DataBar-14 Omnidirectional */
        OMNI,
        /** DataBar-14 Omnidirectional Stacked */
        STACKED
    }

    private static final int[] G_SUM_TABLE = {
        0, 161, 961, 2015, 2715, 0, 336, 1036, 1516
    };

    private static final int[] T_TABLE = {
        1, 10, 34, 70, 126, 4, 20, 48, 81
    };

    private static final int[] MODULES_ODD = {
        12, 10, 8, 6, 4, 5, 7, 9, 11
    };

    private static final int[] MODULES_EVEN = {
        4, 6, 8, 10, 12, 10, 8, 6, 4
    };

    private static final int[] WIDEST_ODD = {
        8, 6, 4, 3, 1, 2, 4, 6, 8
    };

    private static final int[] WIDEST_EVEN = {
        1, 3, 5, 6, 8, 7, 5, 3, 1
    };

    private static final int[] CHECKSUM_WEIGHT = { /* Table 5 */
        1, 3, 9, 27, 2, 6, 18, 54, 4, 12, 36, 29, 8, 24, 72, 58, 16, 48, 65,
        37, 32, 17, 51, 74, 64, 34, 23, 69, 49, 68, 46, 59
    };

    private static final int[] FINDER_PATTERN = {
        3, 8, 2, 1, 1, 3, 5, 5, 1, 1, 3, 3, 7, 1, 1, 3, 1, 9, 1, 1, 2, 7, 4,
        1, 1, 2, 5, 6, 1, 1, 2, 3, 8, 1, 1, 1, 5, 7, 1, 1, 1, 3, 9, 1, 1
    };

    private boolean linkageFlag;
    private int separatorHeight = 1;
    private Mode mode = Mode.LINEAR;

    @Override
    public void setDataType(DataType dummy) {
        // Do nothing!
    }

    /**
     * Although this is a GS1 symbology, input data is expected to omit the [01] Application Identifier,
     * as well as the check digit. Thus, the input data is not considered GS1-format data.
     */
    @Override
    protected boolean gs1Supported() {
        return false;
    }

    protected void setLinkageFlag(boolean linkageFlag) {
        this.linkageFlag = linkageFlag;
    }

    protected boolean getLinkageFlag() {
        return linkageFlag;
    }

    /**
     * Sets the symbol mode. The default is {@link Mode#LINEAR}.
     *
     * @param mode the symbol mode
     */
    public void setMode(Mode mode) {
        this.mode = mode;
    }

    /**
     * Returns the symbol mode.
     *
     * @return the symbol mode
     */
    public Mode getMode() {
        return mode;
    }

    /**
     * Sets the separator height for {@link Mode#STACKED} and {@link Mode#OMNI} symbols. The default value is {@code 1}.
     *
     * @param separatorHeight the separator height for {@link Mode#STACKED} and {@link Mode#OMNI} symbols
     */
    public void setSeparatorHeight(int separatorHeight) {
        if (separatorHeight < 1) {
            throw new IllegalArgumentException("Invalid DataBar-14 Stacked separator height: " + separatorHeight);
        }
        this.separatorHeight = separatorHeight;
    }

    /**
     * Returns the separator height for {@link Mode#STACKED} and {@link Mode#OMNI} symbols.
     *
     * @return the separator height for {@link Mode#STACKED} and {@link Mode#OMNI} symbols
     */
    public int getSeparatorHeight() {
        return separatorHeight;
    }

    @Override
    protected void encode() {

        boolean[][] grid = new boolean[5][100];
        BigInteger accum;
        BigInteger left_reg;
        BigInteger right_reg;
        int[] data_character = new int[4];
        int[] data_group = new int[4];
        int[] v_odd = new int[4];
        int[] v_even = new int[4];
        int i;
        int[][] data_widths = new int[8][4];
        int checksum;
        int c_left;
        int c_right;
        int[] total_widths = new int[46];
        int writer;
        char latch;
        int j;
        int count;
        int check_digit;
        StringBuilder bin = new StringBuilder();
        int compositeOffset = 0;

        if (content.length() > 13) {
            throw new OkapiException("Input too long");
        }

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

        accum = new BigInteger(content);
        if (linkageFlag) {
            accum = accum.add(new BigInteger("10000000000000"));
            compositeOffset = 1;
        }

        /* Calculate left and right pair values */
        left_reg = accum.divide(new BigInteger("4537077"));
        right_reg = accum.mod(new BigInteger("4537077"));

        /* Calculate four data characters */
        accum = left_reg.divide(new BigInteger("1597"));
        data_character[0] = accum.intValue();
        accum = left_reg.mod(new BigInteger("1597"));
        data_character[1] = accum.intValue();
        accum = right_reg.divide(new BigInteger("1597"));
        data_character[2] = accum.intValue();
        accum = right_reg.mod(new BigInteger("1597"));
        data_character[3] = accum.intValue();

        info("Data Characters: ");
        for (i = 0; i < 4; i++) {
            infoSpace(data_character[i]);
        }
        infoLine();

        /* Calculate odd and even subset values */
        if (data_character[0] >= 0 && data_character[0] <= 160) {
            data_group[0] = 0;
        }
        if (data_character[0] >= 161 && data_character[0] <= 960) {
            data_group[0] = 1;
        }
        if (data_character[0] >= 961 && data_character[0] <= 2014) {
            data_group[0] = 2;
        }
        if (data_character[0] >= 2015 && data_character[0] <= 2714) {
            data_group[0] = 3;
        }
        if (data_character[0] >= 2715 && data_character[0] <= 2840) {
            data_group[0] = 4;
        }
        if (data_character[1] >= 0 && data_character[1] <= 335) {
            data_group[1] = 5;
        }
        if (data_character[1] >= 336 && data_character[1] <= 1035) {
            data_group[1] = 6;
        }
        if (data_character[1] >= 1036 && data_character[1] <= 1515) {
            data_group[1] = 7;
        }
        if (data_character[1] >= 1516 && data_character[1] <= 1596) {
            data_group[1] = 8;
        }
        if (data_character[3] >= 0 && data_character[3] <= 335) {
            data_group[3] = 5;
        }
        if (data_character[3] >= 336 && data_character[3] <= 1035) {
            data_group[3] = 6;
        }
        if (data_character[3] >= 1036 && data_character[3] <= 1515) {
            data_group[3] = 7;
        }
        if (data_character[3] >= 1516 && data_character[3] <= 1596) {
            data_group[3] = 8;
        }
        if (data_character[2] >= 0 && data_character[2] <= 160) {
            data_group[2] = 0;
        }
        if (data_character[2] >= 161 && data_character[2] <= 960) {
            data_group[2] = 1;
        }
        if (data_character[2] >= 961 && data_character[2] <= 2014) {
            data_group[2] = 2;
        }
        if (data_character[2] >= 2015 && data_character[2] <= 2714) {
            data_group[2] = 3;
        }
        if (data_character[2] >= 2715 && data_character[2] <= 2840) {
            data_group[2] = 4;
        }

        v_odd[0] = (data_character[0] - G_SUM_TABLE[data_group[0]]) / T_TABLE[data_group[0]];
        v_even[0] = (data_character[0] - G_SUM_TABLE[data_group[0]]) % T_TABLE[data_group[0]];
        v_odd[1] = (data_character[1] - G_SUM_TABLE[data_group[1]]) % T_TABLE[data_group[1]];
        v_even[1] = (data_character[1] - G_SUM_TABLE[data_group[1]]) / T_TABLE[data_group[1]];
        v_odd[3] = (data_character[3] - G_SUM_TABLE[data_group[3]]) % T_TABLE[data_group[3]];
        v_even[3] = (data_character[3] - G_SUM_TABLE[data_group[3]]) / T_TABLE[data_group[3]];
        v_odd[2] = (data_character[2] - G_SUM_TABLE[data_group[2]]) / T_TABLE[data_group[2]];
        v_even[2] = (data_character[2] - G_SUM_TABLE[data_group[2]]) % T_TABLE[data_group[2]];

        /* Use RSS subset width algorithm */
        for (i = 0; i < 4; i++) {
            if ((i == 0) || (i == 2)) {
                int[] widths = getWidths(v_odd[i], MODULES_ODD[data_group[i]], 4, WIDEST_ODD[data_group[i]], 1);
                data_widths[0][i] = widths[0];
                data_widths[2][i] = widths[1];
                data_widths[4][i] = widths[2];
                data_widths[6][i] = widths[3];
                widths = getWidths(v_even[i], MODULES_EVEN[data_group[i]], 4, WIDEST_EVEN[data_group[i]], 0);
                data_widths[1][i] = widths[0];
                data_widths[3][i] = widths[1];
                data_widths[5][i] = widths[2];
                data_widths[7][i] = widths[3];
            } else {
                int[] widths = getWidths(v_odd[i], MODULES_ODD[data_group[i]], 4, WIDEST_ODD[data_group[i]], 0);
                data_widths[0][i] = widths[0];
                data_widths[2][i] = widths[1];
                data_widths[4][i] = widths[2];
                data_widths[6][i] = widths[3];
                widths = getWidths(v_even[i], MODULES_EVEN[data_group[i]], 4, WIDEST_EVEN[data_group[i]], 1);
                data_widths[1][i] = widths[0];
                data_widths[3][i] = widths[1];
                data_widths[5][i] = widths[2];
                data_widths[7][i] = widths[3];
            }
        }

        /* Calculate the checksum */
        checksum = 0;
        for (i = 0; i < 8; i++) {
            checksum += CHECKSUM_WEIGHT[i] * data_widths[i][0];
            checksum += CHECKSUM_WEIGHT[i + 8] * data_widths[i][1];
            checksum += CHECKSUM_WEIGHT[i + 16] * data_widths[i][2];
            checksum += CHECKSUM_WEIGHT[i + 24] * data_widths[i][3];
        }
        checksum %= 79;

        /* Calculate the two check characters */
        if (checksum >= 8) {
            checksum++;
        }
        if (checksum >= 72) {
            checksum++;
        }
        c_left = checksum / 9;
        c_right = checksum % 9;

        infoLine("Checksum: " + checksum);

        /* Put element widths together */
        total_widths[0] = 1;
        total_widths[1] = 1;
        total_widths[44] = 1;
        total_widths[45] = 1;
        for (i = 0; i < 8; i++) {
            total_widths[i + 2] = data_widths[i][0];
            total_widths[i + 15] = data_widths[7 - i][1];
            total_widths[i + 23] = data_widths[i][3];
            total_widths[i + 36] = data_widths[7 - i][2];
        }
        for (i = 0; i < 5; i++) {
            total_widths[i + 10] = FINDER_PATTERN[i + (5 * c_left)];
            total_widths[i + 31] = FINDER_PATTERN[(4 - i) + (5 * c_right)];
        }

        row_count = 0;

        boolean[] separator = new boolean[100];
        for(i = 0; i < separator.length; i++) {
            separator[i] = false;
        }

        /* Put this data into the symbol */
        if (mode == Mode.LINEAR) {
            writer = 0;
            latch = '0';
            for (i = 0; i < 46; i++) {
                for (j = 0; j < total_widths[i]; j++) {
                    if (latch == '1') {
                        grid[row_count][writer] = true;
                    }
                    writer++;
                }
                if (latch == '1') {
                    latch = '0';
                } else {
                    latch = '1';
                }
            }
            if (symbol_width < writer) {
                symbol_width = writer;
            }

            if(linkageFlag) {
                /* separator pattern for composite symbol */
                for(i = 4; i < 92; i++) {
                    separator[i] = (!(grid[0][i]));
                }
                latch = '1';
                for(i = 16; i < 32; i++) {
                    if (!grid[0][i]) {
                        if (latch == '1') {
                            separator[i] = true;
                            latch = '0';
                        } else {
                            separator[i] = false;
                            latch = '1';
                        }
                    } else {
                        separator[i] = false;
                        latch = '1';
                    }
                }
                latch = '1';
                for(i = 63; i < 78; i++) {
                    if (!grid[0][i]) {
                        if (latch == '1') {
                            separator[i] = true;
                            latch = '0';
                        } else {
                            separator[i] = false;
                            latch = '1';
                        }
                    } else {
                        separator[i] = false;
                        latch = '1';
                    }
                }
            }
            row_count = row_count + 1;

            count = 0;
            check_digit = 0;

            /* Calculate check digit from Annex A and place human readable text */
            StringBuilder hrt = new StringBuilder(14);
            for (i = content.length(); i < 13; i++) {
                hrt.append('0');
            }
            hrt.append(content);
            for (i = 0; i < 13; i++) {
                count += hrt.charAt(i) - '0';
                if ((i & 1) == 0) {
                    count += 2 * (hrt.charAt(i) - '0');
                }
            }
            check_digit = 10 - (count % 10);
            if (check_digit == 10) {
                check_digit = 0;
            }
            infoLine("Check Digit: " + check_digit);
            hrt.append((char) (check_digit + '0'));
            readable = "(01)" + hrt;
        }

        if (mode == Mode.STACKED) {
            /* top row */
            writer = 0;
            latch = '0';
            for (i = 0; i < 23; i++) {
                for (j = 0; j < total_widths[i]; j++) {
                    grid[row_count][writer] = (latch == '1');
                    writer++;
                }
                if (latch == '1') {
                    latch = '0';
                } else {
                    latch = '1';
                }
            }
            grid[row_count][writer] = true;
            grid[row_count][writer + 1] = false;

            /* bottom row */
            row_count = row_count + 2;
            grid[row_count][0] = true;
            grid[row_count][1] = false;
            writer = 0;
            latch = '1';
            for (i = 23; i < 46; i++) {
                for (j = 0; j < total_widths[i]; j++) {
                    grid[row_count][writer + 2] = (latch == '1');
                    writer++;
                }
                if (latch == '1') {
                    latch = '0';
                } else {
                    latch = '1';
                }
            }

            /* separator pattern */
            for (i = 1; i < 46; i++) {
                if (grid[row_count - 2][i] == grid[row_count][i]) {
                    if (!grid[row_count - 2][i]) {
                        grid[row_count - 1][i] = true;
                    }
                } else {
                    if (!grid[row_count - 1][i - 1]) {
                        grid[row_count - 1][i] = true;
                    }
                }
            }
            for (i = 0; i < 4; i++) {
                grid[row_count - 1][i] = false;
            }

            if (linkageFlag) {
                /* separator pattern for composite symbol */
                for(i = 4; i < 46; i++) {
                    separator[i] = !grid[0][i];
                }
                latch = '1';
                for(i = 16; i < 32; i++) {
                    if (!grid[0][i]) {
                        if (latch == '1') {
                            separator[i] = true;
                            latch = '0';
                        } else {
                            separator[i] = false;
                            latch = '1';
                        }
                    } else {
                        separator[i] = false;
                        latch = '1';
                    }
                }
            }
            row_count = row_count + 1;
            if (symbol_width < 50) {
                symbol_width = 50;
            }
        }

        if (mode == Mode.OMNI) {
            /* top row */
            writer = 0;
            latch = '0';
            for (i = 0; i < 23; i++) {
                for (j = 0; j < total_widths[i]; j++) {
                    grid[row_count][writer] = (latch == '1');
                    writer++;
                }
                latch = (latch == '1' ? '0' : '1');
            }
            grid[row_count][writer] = true;
            grid[row_count][writer + 1] = false;

            /* bottom row */
            row_count = row_count + 4;
            grid[row_count][0] = true;
            grid[row_count][1] = false;
            writer = 0;
            latch = '1';
            for (i = 23; i < 46; i++) {
                for (j = 0; j < total_widths[i]; j++) {
                    grid[row_count][writer + 2] = (latch == '1');
                    writer++;
                }
                if (latch == '1') {
                    latch = '0';
                } else {
                    latch = '1';
                }
            }

            /* middle separator */
            for (i = 5; i < 46; i += 2) {
                grid[row_count - 2][i] = true;
            }

            /* top separator */
            for (i = 4; i < 46; i++) {
                if (!grid[row_count - 4][i]) {
                    grid[row_count - 3][i] = true;
                }
            }
            latch = '1';
            for (i = 17; i < 33; i++) {
                if (!grid[row_count - 4][i]) {
                    if (latch == '1') {
                        grid[row_count - 3][i] = true;
                        latch = '0';
                    } else {
                        grid[row_count - 3][i] = false;
                        latch = '1';
                    }
                } else {
                    grid[row_count - 3][i] = false;
                    latch = '1';
                }
            }

            /* bottom separator */
            for (i = 4; i < 46; i++) {
                if (!grid[row_count][i]) {
                    grid[row_count - 1][i] = true;
                }
            }
            latch = '1';
            for (i = 16; i < 32; i++) {
                if (!grid[row_count][i]) {
                    if (latch == '1') {
                        grid[row_count - 1][i] = true;
                        latch = '0';
                    } else {
                        grid[row_count - 1][i] = false;
                        latch = '1';
                    }
                } else {
                    grid[row_count - 1][i] = false;
                    latch = '1';
                }
            }

            if (symbol_width < 50) {
                symbol_width = 50;
            }
            if (linkageFlag) {
                /* separator pattern for composite symbol */
                for (i = 4; i < 46; i++) {
                    separator[i] = !grid[0][i];
                }
                latch = '1';
                for(i = 16; i < 32; i++) {
                    if (!grid[0][i]) {
                        if (latch == '1') {
                            separator[i] = true;
                            latch = '0';
                        } else {
                            separator[i] = false;
                            latch = '1';
                        }
                    } else {
                        separator[i] = false;
                        latch = '1';
                    }
                }
            }
            row_count = row_count + 1;
        }

        pattern = new String[row_count + compositeOffset];
        row_height = new int[row_count + compositeOffset];

        if (linkageFlag) {
            bin.setLength(0);
            for (j = 0; j < symbol_width; j++) {
                if (separator[j]) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            pattern[0] = bin2pat(bin);
            row_height[0] = 1;
        }

        for (i = 0; i < row_count; i++) {
            bin.setLength(0);
            for (j = 0; j < symbol_width; j++) {
                if (grid[i][j]) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            pattern[i + compositeOffset] = bin2pat(bin);
        }

        if (mode == Mode.LINEAR) {
            row_height[0 + compositeOffset] = -1;
        }
        if (mode == Mode.STACKED) {
            row_height[0 + compositeOffset] = 5;
            row_height[1 + compositeOffset] = separatorHeight;
            row_height[2 + compositeOffset] = 7;
        }
        if (mode == Mode.OMNI) {
            row_height[0 + compositeOffset] = -1;
            row_height[1 + compositeOffset] = separatorHeight;
            row_height[2 + compositeOffset] = separatorHeight;
            row_height[3 + compositeOffset] = separatorHeight;
            row_height[4 + compositeOffset] = -1;
        }

        if (linkageFlag) {
            row_count++;
        }
    }
} public enum Mode {
    /** DataBar-14 */
    LINEAR,
    /** DataBar-14 Omnidirectional */
    OMNI,
    /** DataBar-14 Omnidirectional Stacked */
    STACKED
} public class DataBarExpanded extends Symbol {

    private static final int[] G_SUM_EXP = {
        0, 348, 1388, 2948, 3988
    };

    private static final int[] T_EVEN_EXP = {
        4, 20, 52, 104, 204
    };

    private static final int[] MODULES_ODD_EXP = {
        12, 10, 8, 6, 4
    };

    private static final int[] MODULES_EVEN_EXP = {
        5, 7, 9, 11, 13
    };

    private static final int[] WIDEST_ODD_EXP = {
        7, 5, 4, 3, 1
    };

    private static final int[] WIDEST_EVEN_EXP = {
        2, 4, 5, 6, 8
    };

    /** Table 14 */
    private static final int[] CHECKSUM_WEIGHT_EXP = {
        1, 3, 9, 27, 81, 32, 96, 77, 20, 60, 180, 118, 143, 7, 21, 63, 189,
        145, 13, 39, 117, 140, 209, 205, 193, 157, 49, 147, 19, 57, 171, 91,
        62, 186, 136, 197, 169, 85, 44, 132, 185, 133, 188, 142, 4, 12, 36,
        108, 113, 128, 173, 97, 80, 29, 87, 50, 150, 28, 84, 41, 123, 158, 52,
        156, 46, 138, 203, 187, 139, 206, 196, 166, 76, 17, 51, 153, 37, 111,
        122, 155, 43, 129, 176, 106, 107, 110, 119, 146, 16, 48, 144, 10, 30,
        90, 59, 177, 109, 116, 137, 200, 178, 112, 125, 164, 70, 210, 208, 202,
        184, 130, 179, 115, 134, 191, 151, 31, 93, 68, 204, 190, 148, 22, 66,
        198, 172, 94, 71, 2, 6, 18, 54, 162, 64, 192, 154, 40, 120, 149, 25,
        75, 14, 42, 126, 167, 79, 26, 78, 23, 69, 207, 199, 175, 103, 98, 83,
        38, 114, 131, 182, 124, 161, 61, 183, 127, 170, 88, 53, 159, 55, 165,
        73, 8, 24, 72, 5, 15, 45, 135, 194, 160, 58, 174, 100, 89
    };

    /** Table 15 */
    private static final int[] FINDER_PATTERN_EXP = {
        1, 8, 4, 1, 1, 1, 1, 4, 8, 1, 3, 6, 4, 1, 1, 1, 1, 4, 6, 3, 3, 4, 6, 1,
        1, 1, 1, 6, 4, 3, 3, 2, 8, 1, 1, 1, 1, 8, 2, 3, 2, 6, 5, 1, 1, 1, 1, 5,
        6, 2, 2, 2, 9, 1, 1, 1, 1, 9, 2, 2
    };

    /** Table 16 */
    private static final int[] FINDER_SEQUENCE = {
        1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6,
        3, 8, 0, 0, 0, 0, 0, 0, 0, 1, 10, 3, 8, 5, 0, 0, 0, 0, 0, 0, 1, 10, 3,
        8, 7, 12, 0, 0, 0, 0, 0, 1, 10, 3, 8, 9, 12, 11, 0, 0, 0, 0, 1, 2, 3,
        4, 5, 6, 7, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 10, 9, 0, 0, 1, 2, 3, 4,
        5, 6, 7, 10, 11, 12, 0, 1, 2, 3, 4, 5, 8, 7, 10, 9, 12, 11
    };

    private static final int[] WEIGHT_ROWS = {
        0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6,
        3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 3, 4,
        13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13,
        14, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, 14,
        11, 12, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, 14,
        15, 16, 21, 22, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7,
        8, 9, 10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
        9, 10, 11, 12, 17, 18, 15, 16, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
        9, 10, 11, 12, 17, 18, 19, 20, 21, 22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
        13, 14, 11, 12, 17, 18, 15, 16, 21, 22, 19, 20
    };

    private enum EncodeMode {
        NUMERIC, ALPHA, ISOIEC, INVALID_CHAR, ANY_ENC, ALPHA_OR_ISO
    }

    private boolean linkageFlag;
    private int preferredColumns = 2;
    private boolean stacked = true;

    public DataBarExpanded() {
        inputDataType = DataType.GS1;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    /**
     * Sets the preferred width of a stacked symbol by selecting the number of "columns" or symbol segments in each row of data.
     *
     * @param columns the number of segments in each row
     */
    public void setPreferredColumns(int columns) {
        if (columns < 1 || columns > 10) {
            throw new IllegalArgumentException("Invalid column count: " + columns);
        }
        preferredColumns = columns;
    }

    /**
     * Returns the preferred width of a stacked symbol by selecting the number of "columns" or symbol segments in each row of data.
     *
     * @return the number of segments in each row
     */
    public int getPreferredColumns() {
        return preferredColumns;
    }

    /**
     * Sets whether or not this symbology is stacked.
     *
     * @param stacked {@code true} for GS1 DataBar Expanded Stacked Omnidirectional, {@code false} for GS1 DataBar Expanded Omnidirectional
     */
    public void setStacked(boolean stacked) {
        this.stacked = stacked;
    }

    /**
     * Returns whether or not this symbology is stacked.
     *
     * @return {@code true} for GS1 DataBar Expanded Stacked Omnidirectional, {@code false} for GS1 DataBar Expanded Omnidirectional
     */
    public boolean isStacked() {
        return stacked;
    }

    protected void setLinkageFlag(boolean linkageFlag) {
        this.linkageFlag = linkageFlag;
    }

    @Override
    protected void encode() {
        int i;
        int j;
        int k;
        int data_chars;
        int[] vs = new int[21];
        int[] group = new int[21];
        int[] v_odd = new int[21];
        int[] v_even = new int[21];
        int[][] char_widths = new int[21][8];
        int checksum;
        int row;
        int check_char;
        int c_group;
        int c_odd;
        int c_even;
        int[] check_widths = new int[8];
        int pattern_width;
        int[] elements = new int[235];
        int codeblocks;
        int stack_rows;
        int blocksPerRow;
        int current_block;
        int current_row;
        boolean special_case_row;
        int num_columns;
        int elements_in_sub;
        int reader;
        int[] sub_elements = new int[235];
        int symbol_row;
        boolean black;
        boolean left_to_right;
        int compositeOffset;

        inputData = toBytes(content, StandardCharsets.US_ASCII);
        if (inputData == null) {
            throw new OkapiException("Invalid characters in input data");
        }

        StringBuilder binaryString = new StringBuilder(inputData.length * 8);

        if (linkageFlag) {
            binaryString.append('1');
            compositeOffset = 1;
        } else {
            binaryString.append('0');
            compositeOffset = 0;
        }

        int encodingMethod = calculateBinaryString(stacked, preferredColumns, inputData, binaryString); // updates binaryString
        infoLine("Encoding Method: " + encodingMethod);
        logBinaryStringInfo(binaryString);

        data_chars = binaryString.length() / 12;

        info("Data Characters: ");
        for (i = 0; i < data_chars; i++) {
            vs[i] = 0;
            for (j = 0; j < 12; j++) {
                if (binaryString.charAt((i * 12) + j) == '1') {
                    vs[i] += 2048 >> j;
                }
            }
            infoSpace(vs[i]);
        }
        infoLine();

        for (i = 0; i < data_chars; i++) {
            if (vs[i] <= 347) {
                group[i] = 1;
            }
            if ((vs[i] >= 348) && (vs[i] <= 1387)) {
                group[i] = 2;
            }
            if ((vs[i] >= 1388) && (vs[i] <= 2947)) {
                group[i] = 3;
            }
            if ((vs[i] >= 2948) && (vs[i] <= 3987)) {
                group[i] = 4;
            }
            if (vs[i] >= 3988) {
                group[i] = 5;
            }
            v_odd[i] = (vs[i] - G_SUM_EXP[group[i] - 1]) / T_EVEN_EXP[group[i] - 1];
            v_even[i] = (vs[i] - G_SUM_EXP[group[i] - 1]) % T_EVEN_EXP[group[i] - 1];

            int[] widths = getWidths(v_odd[i], MODULES_ODD_EXP[group[i] - 1], 4, WIDEST_ODD_EXP[group[i] - 1], 0);
            char_widths[i][0] = widths[0];
            char_widths[i][2] = widths[1];
            char_widths[i][4] = widths[2];
            char_widths[i][6] = widths[3];

            widths = getWidths(v_even[i], MODULES_EVEN_EXP[group[i] - 1], 4, WIDEST_EVEN_EXP[group[i] - 1], 1);
            char_widths[i][1] = widths[0];
            char_widths[i][3] = widths[1];
            char_widths[i][5] = widths[2];
            char_widths[i][7] = widths[3];
        }

        /* 7.2.6 Check character */
        /* The checksum value is equal to the mod 211 residue of the weighted sum of the widths of the elements in the data characters. */
        checksum = 0;
        for (i = 0; i < data_chars; i++) {
            row = WEIGHT_ROWS[(((data_chars - 2) / 2) * 21) + i];
            for (j = 0; j < 8; j++) {
                checksum += (char_widths[i][j] * CHECKSUM_WEIGHT_EXP[(row * 8) + j]);

            }
        }

        check_char = (211 * ((data_chars + 1) - 4)) + (checksum % 211);

        infoLine("Check Character: " + check_char);

        c_group = 1;
        if ((check_char >= 348) && (check_char <= 1387)) {
            c_group = 2;
        }
        if ((check_char >= 1388) && (check_char <= 2947)) {
            c_group = 3;
        }
        if ((check_char >= 2948) && (check_char <= 3987)) {
            c_group = 4;
        }
        if (check_char >= 3988) {
            c_group = 5;
        }

        c_odd = (check_char - G_SUM_EXP[c_group - 1]) / T_EVEN_EXP[c_group - 1];
        c_even = (check_char - G_SUM_EXP[c_group - 1]) % T_EVEN_EXP[c_group - 1];

        int[] widths = getWidths(c_odd, MODULES_ODD_EXP[c_group - 1], 4, WIDEST_ODD_EXP[c_group - 1], 0);
        check_widths[0] = widths[0];
        check_widths[2] = widths[1];
        check_widths[4] = widths[2];
        check_widths[6] = widths[3];

        widths = getWidths(c_even, MODULES_EVEN_EXP[c_group - 1], 4, WIDEST_EVEN_EXP[c_group - 1], 1);
        check_widths[1] = widths[0];
        check_widths[3] = widths[1];
        check_widths[5] = widths[2];
        check_widths[7] = widths[3];

        /* Initialise element array */
        pattern_width = ((((data_chars + 1) / 2) + ((data_chars + 1) & 1)) * 5) + ((data_chars + 1) * 8) + 4;
        for (i = 0; i < pattern_width; i++) {
            elements[i] = 0;
        }

        /* Put finder patterns in element array */
        for (i = 0; i < (((data_chars + 1) / 2) + ((data_chars + 1) & 1)); i++) {
            k = ((((((data_chars + 1) - 2) / 2) + ((data_chars + 1) & 1)) - 1) * 11) + i;
            for (j = 0; j < 5; j++) {
                elements[(21 * i) + j + 10] = FINDER_PATTERN_EXP[((FINDER_SEQUENCE[k] - 1) * 5) + j];
            }
        }

        /* Put check character in element array */
        for (i = 0; i < 8; i++) {
            elements[i + 2] = check_widths[i];
        }

        /* Put forward reading data characters in element array */
        for (i = 1; i < data_chars; i += 2) {
            for (j = 0; j < 8; j++) {
                elements[(((i - 1) / 2) * 21) + 23 + j] = char_widths[i][j];
            }
        }

        /* Put reversed data characters in element array */
        for (i = 0; i < data_chars; i += 2) {
            for (j = 0; j < 8; j++) {
                elements[((i / 2) * 21) + 15 + j] = char_widths[i][7 - j];
            }
        }

        if (!stacked) {

            /* Add left and right guards */
            elements[0] = 1;
            elements[1] = 1;
            elements[pattern_width - 2] = 1;
            elements[pattern_width - 1] = 1;

            /* Copy elements into symbol */
            row_count = 1 + compositeOffset;
            row_height = new int[1 + compositeOffset];
            row_height[0 + compositeOffset] = -1;
            pattern = new String[1 + compositeOffset];

            black = false;
            StringBuilder pat = new StringBuilder("0");
            for (i = 0; i < pattern_width; i++) {
                pat.append((char) (elements[i] + '0'));
                black = !black;
            }
            pattern[0 + compositeOffset] = pat.toString();

        } else {

            /* RSS Expanded Stacked */
            codeblocks = (data_chars + 1) / 2 + ((data_chars + 1) % 2);

            blocksPerRow = preferredColumns;
            if (linkageFlag && blocksPerRow == 1) {
                /* "There shall be a minimum of four symbol characters in the
                first row of an RSS Expanded Stacked symbol when it is the linear
                component of an EAN.UCC Composite symbol." */
                blocksPerRow = 2;
            }

            stack_rows = codeblocks / blocksPerRow;
            if (codeblocks % blocksPerRow > 0) {
                stack_rows++;
            }

            row_count = (stack_rows * 4) - 3;
            row_height = new int[row_count + compositeOffset];
            pattern = new String[row_count + compositeOffset];

            symbol_row = 0;
            current_block = 0;
            AtomicBoolean v2 = new AtomicBoolean(false);

            for (current_row = 1; current_row <= stack_rows; current_row++) {

                Arrays.fill(sub_elements, 0);
                special_case_row = false;
                num_columns = (current_row < stack_rows ? blocksPerRow : codeblocks - current_block);

                /* Row Start */
                sub_elements[0] = 1;
                sub_elements[1] = 1;
                elements_in_sub = 2;

                left_to_right = (current_row  % 2 == 1) || // odd row
                                (blocksPerRow % 2 == 1);   // odd number of segment pairs per row

                if (!left_to_right &&              // row should be mirrored
                    current_row == stack_rows &&   // last row
                    num_columns != blocksPerRow && // partial width row
                    num_columns % 2 == 1) {        // odd number of finder patterns / columns
                    special_case_row = true;
                    left_to_right = true;
                    sub_elements[0] = 2;
                } else {
                    special_case_row = false;
                }

                /* Row Data */
                reader = 0;
                do {
                    i = 2 + (current_block * 21);
                    for (j = 0; j < 21; j++) {
                        if (i + j < pattern_width) {
                            if (left_to_right) {
                                sub_elements[j + (reader * 21) + 2] = elements[i + j];
                            } else {
                                sub_elements[(20 - j) + (num_columns - 1 - reader) * 21 + 2] = elements[i + j];
                            }
                        }
                        elements_in_sub++;
                    }
                    reader++;
                    current_block++;
                } while (reader < blocksPerRow && current_block < codeblocks);

                /* Row Stop */
                sub_elements[elements_in_sub] = 1;
                sub_elements[elements_in_sub + 1] = 1;
                elements_in_sub += 2;

                black = true;
                row_height[symbol_row + compositeOffset] = -1;
                StringBuilder pat = new StringBuilder();
                if (current_row % 2 == 1 || special_case_row) {
                    pat.append('0');
                    black = false;
                }
                for (i = 0; i < elements_in_sub; i++) {
                    pat.append((char) (sub_elements[i] + '0'));
                    black = !black;
                }
                pattern[symbol_row + compositeOffset] = pat.toString();

                if (current_row != 1) {
                    /* middle separator pattern (above current row) */
                    StringBuilder sep = new StringBuilder("05");
                    for (j = 5; j < (49 * blocksPerRow); j += 2) {
                        sep.append("11");
                    }
                    pattern[symbol_row - 2 + compositeOffset] = sep.toString();
                    row_height[symbol_row - 2 + compositeOffset] = 1;
                    /* bottom separator pattern (above current row) */
                    boolean odd_last_row = (current_row == stack_rows) && (data_chars % 2 == 0);
                    row_height[symbol_row - 1 + compositeOffset] = 1;
                    pattern[symbol_row - 1 + compositeOffset] = separator(pat, reader, false, special_case_row, left_to_right, odd_last_row, v2);
                }

                if (current_row != stack_rows) {
                    /* top separator pattern (below current row) */
                    row_height[symbol_row + 1 + compositeOffset] = 1;
                    pattern[symbol_row + 1 + compositeOffset] = separator(pat, reader, true, false, left_to_right, false, v2);
                }

                symbol_row += 4;

            } // end current_row loop

            readable = "";
            row_count += compositeOffset;
        }

        if (linkageFlag) {
            // Add composite code separator
            pattern[0] = separator(pattern[1], 4, false, false, true, false, new AtomicBoolean(false));
            row_height[0] = 1;
        }
    }

    private static String separator(CharSequence pattern, int cols, boolean below, boolean specialCaseRow,
                    boolean leftToRight, boolean oddLastRow, AtomicBoolean v2mutable) {

        // start with the complement of the linear symbol
        StringBuilder linearBin = new StringBuilder();
        StringBuilder separator = new StringBuilder();
        boolean black = true;
        for (int i = 0; i < pattern.length(); i++) {
            int c = pattern.charAt(i) - '0';
            for (int j = 0; j < c; j++) {
                linearBin.append(black ? '1' : '0');
                separator.append(black ? '0' : '1');
            }
            black = !black;
        }

        // clear first 4 and last 4 modules
        for (int i = 0; i < 4; i++) {
            separator.setCharAt(i, '0');
            separator.setCharAt(separator.length() - 1 - i, '0');
        }

        // finder adjustments
        boolean space = false;
        boolean v2 = v2mutable.get();
        for (int j = 0; j < cols; j++) {
            // 49 == data (17) + finder (15) + data(17) triplet
            // 19 == 2 (guard) + 17 (initial check/data character)
            int k = (49 * j) + 19 + (specialCaseRow ? 1 : 0);
            if (leftToRight) {
                // version 1 finder: first 13 modules
                // version 2 finder: last 13 modules
                int start = v2 ? 2 : 0;
                int end = v2 ? 15 : 13;
                for (int i = start; i < end; i++) {
                    if (i + k < linearBin.length()) {
                        if (linearBin.charAt(i + k) == '1') {
                            separator.setCharAt(i + k, '0');
                            space = false;
                        } else {
                            separator.setCharAt(i + k, space ? '0' : '1');
                            space = !space;
                        }
                    }
                }
            } else {
                if (oddLastRow) {
                    // no data char at beginning of row (ends with finder)
                    k -= 17;
                }
                // version 1 finder: first 13 modules
                // version 2 finder: last 13 modules
                int start = v2 ? 14 : 12;
                int end = v2 ? 2 : 0;
                for (int i = start; i >= end; i--) {
                    if (i + k < linearBin.length()) {
                        if (linearBin.charAt(i + k) == '1') {
                            separator.setCharAt(i + k, '0');
                            space = false;
                        } else {
                            separator.setCharAt(i + k, space ? '0' : '1');
                            space = !space;
                        }
                    }
                }
            }
            v2 = !v2;
        }

        if (below) {
            v2mutable.set(v2);
        }

        return bin2pat(separator);
    }

    /** Handles all data encodation from section 7.2.5 of ISO/IEC 24724. */
    private static int calculateBinaryString(boolean stacked, int blocksPerRow, int[] inputData, StringBuilder binaryString) {

        EncodeMode last_mode = EncodeMode.NUMERIC;
        int i;
        boolean latch;
        int remainder, d1, d2, value;
        String padstring;
        int current_length;

        /* Decide whether a compressed data field is required and if so what method to use: method 2 = no compressed data field */

        int encodingMethod;
        if (inputData.length >= 16 && inputData[0] == '0' && inputData[1] == '1') {
            /* (01) and other AIs */
            encodingMethod = 1;
        } else {
            /* any AIs */
            encodingMethod = 2;
        }

        if (inputData.length >= 20 && encodingMethod == 1 && inputData[2] == '9' && inputData[16] == '3') {

            /* Possibly encoding method > 2 */

            if (inputData.length >= 26 && inputData[17] == '1') {
                /* Methods 3, 7, 9, 11 and 13 */
                if (inputData[18] == '0') {
                    /* (01) and (310x), weight in kilos */
                    double weight = 0;
                    for (i = 0; i < 6; i++) {
                        weight *= 10;
                        weight += (inputData[20 + i] - '0');
                    }
                    if (weight < 99_999) { /* Maximum weight = 99999 */
                        if (inputData[19] == '3' && inputData.length == 26) {
                            /* (01) and (3103) */
                            weight /= 1000.0;
                            if (weight <= 32.767) {
                                encodingMethod = 3;
                            }
                        }
                        if (inputData.length == 34) {
                            if (inputData[26] == '1' && inputData[27] == '1') {
                                /* (01), (310x) and (11) - metric weight and production date */
                                encodingMethod = 7;
                            }
                            if (inputData[26] == '1' && inputData[27] == '3') {
                                /* (01), (310x) and (13) - metric weight and packaging date */
                                encodingMethod = 9;
                            }
                            if (inputData[26] == '1' && inputData[27] == '5') {
                                /* (01), (310x) and (15) - metric weight and "best before" date */
                                encodingMethod = 11;
                            }
                            if (inputData[26] == '1' && inputData[27] == '7') {
                                /* (01), (310x) and (17) - metric weight and expiration date */
                                encodingMethod = 13;
                            }
                        }
                    }
                }
            }

            if (inputData.length >= 26 && inputData[17] == '2') {
                /* Methods 4, 8, 10, 12 and 14 */
                if (inputData[18] == '0') {
                    /* (01) and (320x), weight in pounds */
                    double weight = 0;
                    for (i = 0; i < 6; i++) {
                        weight *= 10;
                        weight += (inputData[20 + i] - '0');
                    }
                    if (weight < 99_999) { /* Maximum weight = 99999 */
                        if ((inputData[19] == '2' || inputData[19] == '3') && (inputData.length == 26)) {
                            /* (01) and (3202)/(3203) */
                            if (inputData[19] == '3') {
                                weight /= 1000.0;
                                if (weight <= 22.767) {
                                    encodingMethod = 4;
                                }
                            } else {
                                weight /= 100.0;
                                if (weight <= 99.99) {
                                    encodingMethod = 4;
                                }
                            }
                        }
                        if (inputData.length == 34) {
                            if (inputData[26] == '1' && inputData[27] == '1') {
                                /* (01), (320x) and (11) - English weight and production date */
                                encodingMethod = 8;
                            }
                            if (inputData[26] == '1' && inputData[27] == '3') {
                                /* (01), (320x) and (13) - English weight and packaging date */
                                encodingMethod = 10;
                            }
                            if (inputData[26] == '1' && inputData[27] == '5') {
                                /* (01), (320x) and (15) - English weight and "best before" date */
                                encodingMethod = 12;
                            }
                            if (inputData[26] == '1' && inputData[27] == '7') {
                                /* (01), (320x) and (17) - English weight and expiration date */
                                encodingMethod = 14;
                            }
                        }
                    }
                }
            }

            if (inputData[17] == '9') {
                /* Methods 5 and 6 */
                if (inputData[18] == '2' && inputData[19] >= '0' && inputData[19] <= '3') {
                    /* (01) and (392x) */
                    encodingMethod = 5;
                }
                if (inputData[18] == '3' && inputData[19] >= '0' && inputData[19] <= '3') {
                    /* (01) and (393x) */
                    encodingMethod = 6;
                }
            }
        }

        /* Encoding method - Table 10 */
        /* Variable length symbol bit field is just given a place holder (XX) for the time being */
        int read_posn;
        switch (encodingMethod) {
            case 1:
                binaryString.append("1XX");
                read_posn = 16;
                break;
            case 2:
                binaryString.append("00XX");
                read_posn = 0;
                break;
            case 3:
                binaryString.append("0100");
                read_posn = inputData.length;
                break;
            case 4:
                binaryString.append("0101");
                read_posn = inputData.length;
                break;
            case 5:
                binaryString.append("01100XX");
                read_posn = 20;
                break;
            case 6:
                binaryString.append("01101XX");
                read_posn = 23;
                break;
            default: /* modes 7 (0111000) to 14 (0111111) */
                binaryString.append("0" + Integer.toBinaryString(56 + encodingMethod - 7));
                read_posn = inputData.length;
                break;
        }

        /* Verify that the data to be placed in the compressed data field is all numeric data before carrying out compression */
        for (i = 0; i < read_posn; i++) {
            if (inputData[i] < '0' || inputData[i] > '9') {
                /* Something is wrong */
                throw new OkapiException("Invalid characters in input data");
            }
        }

        /* Now encode the compressed data field */

        if (encodingMethod == 1) {
            /* Encoding method field "1" - general item identification data */
            binaryAppend(binaryString, inputData[2] - '0', 4);
            for (i = 1; i < 5; i++) {
                int group = parseInt(inputData, i * 3, 3);
                binaryAppend(binaryString, group, 10);
            }
        }

        if (encodingMethod == 3 || encodingMethod == 4) {
            /* Encoding method field "0100" - variable weight item (0,001 kilogram increments) */
            /* Encoding method field "0101" - variable weight item (0,01 or 0,001 pound increment) */
            for (i = 1; i < 5; i++) {
                int group = parseInt(inputData, i * 3, 3);
                binaryAppend(binaryString, group, 10);
            }
            int group = parseInt(inputData, 20, 6);
            if (encodingMethod == 4 && inputData[19] == '3') {
                group += 10_000;
            }
            binaryAppend(binaryString, group, 15);
        }

        if (encodingMethod == 5 || encodingMethod == 6) {
            /* Encoding method field "01100" - variable measure item and price */
            /* Encoding method "01101" - variable measure item and price with ISO 4217 currency code */
            for (i = 1; i < 5; i++) {
                int group = parseInt(inputData, i * 3, 3);
                binaryAppend(binaryString, group, 10);
            }
            binaryAppend(binaryString, inputData[19] - '0', 2);
            if (encodingMethod == 6) {
                int currency = parseInt(inputData, 20, 3);
                binaryAppend(binaryString, currency, 10);
            }
        }

        if (encodingMethod >= 7 && encodingMethod <= 14) {
            /* Encoding method fields "0111000" through "0111111" - variable weight item plus date */
            for (i = 1; i < 5; i++) {
                int group = parseInt(inputData, i * 3, 3);
                binaryAppend(binaryString, group, 10);
            }
            int weight = inputData[19] - '0';
            for (i = 0; i < 5; i++) {
                weight *= 10;
                weight += inputData[21 + i] - '0';
            }
            binaryAppend(binaryString, weight, 20);
            int date;
            if (inputData.length == 34) {
                /* Date information is included */
                date = parseInt(inputData, 28, 2) * 384;
                date += (parseInt(inputData, 30, 2) - 1) * 32;
                date += parseInt(inputData, 32, 2);
            } else {
                date = 38_400;
            }
            binaryAppend(binaryString, date, 16);
        }

        /* The compressed data field has been processed if appropriate - the rest of the data (if any) goes into a general-purpose data compaction field */

        int[] generalField = Arrays.copyOfRange(inputData, read_posn, inputData.length);

        if (generalField.length != 0) {

            latch = false;
            EncodeMode[] generalFieldType = new EncodeMode[generalField.length];

            for (i = 0; i < generalField.length; i++) {
                /* Tables 11, 12, 13 - ISO/IEC 646 encodation */
                int c = generalField[i];
                EncodeMode mode;
                if (c == FNC1) {
                    // FNC1 can be encoded in any system
                    mode = EncodeMode.ANY_ENC;
                } else if (c >= '0' && c <= '9') {
                    // numbers can be encoded in any system, but will usually narrow down to numeric encodation
                    mode = EncodeMode.ANY_ENC;
                } else if ((c >= 'A' && c <= 'Z') || c == '*' || c == ',' || c == '-' || c == '.' || c == '/') {
                    // alphanumeric encodation or ISO/IEC encodation
                    mode = EncodeMode.ALPHA_OR_ISO;
                } else if ((c >= 'a' && c <= 'z') || c == '!' || c == '"' || c == '%' || c == '&' || c == '\'' ||
                            c == '(' || c == ')' || c == '+' || c == ':' || c == ';' || c == '<' || c == '=' ||
                            c == '>' || c == '?' || c == '_' || c == ' ') {
                    // ISO/IEC encodation
                    mode = EncodeMode.ISOIEC;
                } else {
                    // unable to encode this character
                    mode = EncodeMode.INVALID_CHAR;
                    latch = true;
                }
                generalFieldType[i] = mode;
            }

            if (latch) {
                throw new OkapiException("Invalid characters in input data");
            }

            for (i = 0; i < generalField.length - 1; i++) {
                if (generalFieldType[i] == EncodeMode.ISOIEC && generalField[i + 1] == FNC1) {
                    generalFieldType[i + 1] = EncodeMode.ISOIEC;
                }
            }

            for (i = 0; i < generalField.length - 1; i++) {
                if (generalFieldType[i] == EncodeMode.ALPHA_OR_ISO && generalField[i + 1] == FNC1) {
                    generalFieldType[i + 1] = EncodeMode.ALPHA_OR_ISO;
                }
            }

            latch = applyGeneralFieldRules(generalFieldType); // modifies generalFieldType

            /* Set initial mode if not NUMERIC */
            if (generalFieldType[0] == EncodeMode.ALPHA) {
                binaryString.append("0000"); /* Alphanumeric latch */
                last_mode = EncodeMode.ALPHA;
            }
            if (generalFieldType[0] == EncodeMode.ISOIEC) {
                binaryString.append("0000"); /* Alphanumeric latch */
                binaryString.append("00100"); /* ISO/IEC 646 latch */
                last_mode = EncodeMode.ISOIEC;
            }

            i = 0;

            current_length = i;
            if (latch) {
                current_length++;
            }

            while (current_length < generalField.length) {

                switch (generalFieldType[i]) {
                case NUMERIC:
                    if (last_mode != EncodeMode.NUMERIC) {
                        binaryString.append("000"); /* Numeric latch */
                    }
                    if (generalField[i] != FNC1) {
                        d1 = generalField[i] - '0';
                    } else {
                        d1 = 10;
                    }
                    if (generalField[i + 1] != FNC1) {
                        d2 = generalField[i + 1] - '0';
                    } else {
                        d2 = 10;
                    }
                    value = (11 * d1) + d2 + 8;
                    binaryAppend(binaryString, value, 7);
                    i += 2;
                    last_mode = EncodeMode.NUMERIC;
                    break;

                case ALPHA:
                    if (i != 0) {
                        if (last_mode == EncodeMode.NUMERIC) {
                            binaryString.append("0000"); /* Alphanumeric latch */
                        }
                        if (last_mode == EncodeMode.ISOIEC) {
                            binaryString.append("00100"); /* Alphanumeric latch */
                        }
                    }
                    if (generalField[i] >= '0' && generalField[i] <= '9') {
                        value = generalField[i] - 43;
                        binaryAppend(binaryString, value, 5);
                    }
                    if (generalField[i] >= 'A' && generalField[i] <= 'Z') {
                        value = generalField[i] - 33;
                        binaryAppend(binaryString, value, 6);
                    }
                    last_mode = EncodeMode.ALPHA;
                    if (generalField[i] == FNC1) {
                        binaryString.append("01111");
                        // TODO: FNC1 should act as an implicit numeric latch, so the commented out line below should be correct, but ZXing cannot
                        // read barcodes which use FNC1 as an implicit numeric latch... so for now, and in order to achieve widest compatibility,
                        // we waste 3 bits and don't perform the implicit mode change (see https://sourceforge.net/p/zint/tickets/145/)
                        // last_mode = EncodeMode.NUMERIC;
                    } /* FNC1 / Numeric latch */
                    if (generalField[i] == '*') binaryString.append("111010"); /* asterisk */
                    if (generalField[i] == ',') binaryString.append("111011"); /* comma */
                    if (generalField[i] == '-') binaryString.append("111100"); /* minus or hyphen */
                    if (generalField[i] == '.') binaryString.append("111101"); /* period or full stop */
                    if (generalField[i] == '/') binaryString.append("111110"); /* slash or solidus */
                    i++;
                    break;

                case ISOIEC:
                    if (i != 0) {
                        if (last_mode == EncodeMode.NUMERIC) {
                            binaryString.append("0000"); /* Alphanumeric latch */
                            binaryString.append("00100"); /* ISO/IEC 646 latch */
                        }
                        if (last_mode == EncodeMode.ALPHA) {
                            binaryString.append("00100"); /* ISO/IEC 646 latch */
                        }
                    }
                    if (generalField[i] >= '0' && generalField[i] <= '9') {
                        value = generalField[i] - 43;
                        binaryAppend(binaryString, value, 5);
                    }
                    if (generalField[i] >= 'A' && generalField[i] <= 'Z') {
                        value = generalField[i] - 1;
                        binaryAppend(binaryString, value, 7);
                    }
                    if (generalField[i] >= 'a' && generalField[i] <= 'z') {
                        value = generalField[i] - 7;
                        binaryAppend(binaryString, value, 7);
                    }
                    last_mode = EncodeMode.ISOIEC;
                    if (generalField[i] == FNC1) {
                        binaryString.append("01111");
                        // TODO: FNC1 should act as an implicit numeric latch, so the commented out line below should be correct, but ZXing cannot
                        // read barcodes which use FNC1 as an implicit numeric latch... so for now, and in order to achieve widest compatibility,
                        // we waste 3 bits and don't perform the implicit mode change (see https://sourceforge.net/p/zint/tickets/145/)
                        // last_mode = EncodeMode.NUMERIC;
                    } /* FNC1 / Numeric latch */
                    if (generalField[i] == '!') binaryString.append("11101000"); /* exclamation mark */
                    if (generalField[i] == 34)  binaryString.append("11101001"); /* quotation mark */
                    if (generalField[i] == 37)  binaryString.append("11101010"); /* percent sign */
                    if (generalField[i] == '&') binaryString.append("11101011"); /* ampersand */
                    if (generalField[i] == 39)  binaryString.append("11101100"); /* apostrophe */
                    if (generalField[i] == '(') binaryString.append("11101101"); /* left parenthesis */
                    if (generalField[i] == ')') binaryString.append("11101110"); /* right parenthesis */
                    if (generalField[i] == '*') binaryString.append("11101111"); /* asterisk */
                    if (generalField[i] == '+') binaryString.append("11110000"); /* plus sign */
                    if (generalField[i] == ',') binaryString.append("11110001"); /* comma */
                    if (generalField[i] == '-') binaryString.append("11110010"); /* minus or hyphen */
                    if (generalField[i] == '.') binaryString.append("11110011"); /* period or full stop */
                    if (generalField[i] == '/') binaryString.append("11110100"); /* slash or solidus */
                    if (generalField[i] == ':') binaryString.append("11110101"); /* colon */
                    if (generalField[i] == ';') binaryString.append("11110110"); /* semicolon */
                    if (generalField[i] == '<') binaryString.append("11110111"); /* less-than sign */
                    if (generalField[i] == '=') binaryString.append("11111000"); /* equals sign */
                    if (generalField[i] == '>') binaryString.append("11111001"); /* greater-than sign */
                    if (generalField[i] == '?') binaryString.append("11111010"); /* question mark */
                    if (generalField[i] == '_') binaryString.append("11111011"); /* underline or low line */
                    if (generalField[i] == ' ') binaryString.append("11111100"); /* space */
                    i++;
                    break;
                }

                current_length = i;
                if (latch) {
                    current_length++;
                }
            }

            remainder = calculateRemainder(binaryString.length(), stacked, blocksPerRow);

            if (latch) {
                /* There is still one more numeric digit to encode */
                if (last_mode == EncodeMode.NUMERIC) {
                    if (remainder >= 4 && remainder <= 6) {
                        value = generalField[i] - '0';
                        value++;
                        binaryAppend(binaryString, value, 4);
                    } else {
                        d1 = generalField[i] - '0';
                        d2 = 10;
                        value = (11 * d1) + d2 + 8;
                        binaryAppend(binaryString, value, 7);
                    }
                } else {
                    value = generalField[i] - 43;
                    binaryAppend(binaryString, value, 5);
                }
            }
        }

        if (binaryString.length() > 252) {
            throw new OkapiException("Input too long");
        }

        remainder = calculateRemainder(binaryString.length(), stacked, blocksPerRow);

        /* Now add padding to binary string (7.2.5.5.4) */
        i = remainder;
        if (last_mode == EncodeMode.NUMERIC) {
            padstring = "0000";
            i -= 4;
        } else {
            padstring = "";
        }
        for (; i > 0; i -= 5) {
            padstring += "00100";
        }
        binaryString.append(padstring.substring(0, remainder));

        /* Patch variable length symbol bit field */
        char patchEvenOdd, patchSize;
        if ((((binaryString.length() / 12) + 1) & 1) == 0) {
            patchEvenOdd = '0';
        } else {
            patchEvenOdd = '1';
        }
        if (binaryString.length() <= 156) {
            patchSize = '0';
        } else {
            patchSize = '1';
        }

        if (encodingMethod == 1) {
            binaryString.setCharAt(2, patchEvenOdd);
            binaryString.setCharAt(3, patchSize);
        }
        if (encodingMethod == 2) {
            binaryString.setCharAt(3, patchEvenOdd);
            binaryString.setCharAt(4, patchSize);
        }
        if (encodingMethod == 5 || encodingMethod == 6) {
            binaryString.setCharAt(6, patchEvenOdd);
            binaryString.setCharAt(7, patchSize);
        }

        return encodingMethod;
    }

    private static int calculateRemainder(int binaryStringLength, boolean stacked, int blocksPerRow) {
        int remainder = 12 - (binaryStringLength % 12);
        if (remainder == 12) {
            remainder = 0;
        }
        if (binaryStringLength < 36) {
            remainder = 36 - binaryStringLength;
        }
        if (stacked) {
            int symbolChars = ((binaryStringLength + remainder) / 12) + 1; // +1 for check digit
            int symbolCharsInLastRow = symbolChars % (blocksPerRow * 2);
            if (symbolCharsInLastRow == 1) {
                // 7.2.8: The last row shall contain a minimum of two symbol characters with extra padding, if needed.
                remainder += 12;
            }
        }
        return remainder;
    }

    /** Logs binary string as hexadecimal */
    private void logBinaryStringInfo(StringBuilder binaryString) {

        infoLine("Binary Length: " + binaryString.length());
        info("Binary String: ");

        int nibble = 0;
        for (int i = 0; i < binaryString.length(); i++) {
            switch (i % 4) {
                case 0:
                    if (binaryString.charAt(i) == '1') {
                        nibble += 8;
                    }
                    break;
                case 1:
                    if (binaryString.charAt(i) == '1') {
                        nibble += 4;
                    }
                    break;
                case 2:
                    if (binaryString.charAt(i) == '1') {
                        nibble += 2;
                    }
                    break;
                case 3:
                    if (binaryString.charAt(i) == '1') {
                        nibble += 1;
                    }
                    info(Integer.toHexString(nibble));
                    nibble = 0;
                    break;
            }
        }

        if ((binaryString.length() % 4) != 0) {
            info(Integer.toHexString(nibble));
        }

        infoLine();
    }

    /** Attempts to apply encoding rules from sections 7.2.5.5.1 to 7.2.5.5.3 of ISO/IEC 24724:2006 */
    private static boolean applyGeneralFieldRules(EncodeMode[] generalFieldType) {

        int block_count, i, j, k;
        EncodeMode current, next, last;
        int[] blockLength = new int[200];
        EncodeMode[] blockType = new EncodeMode[200];

        block_count = 0;

        blockLength[block_count] = 1;
        blockType[block_count] = generalFieldType[0];

        for (i = 1; i < generalFieldType.length; i++) {
            current = generalFieldType[i];
            last = generalFieldType[i - 1];

            if (current == last) {
                blockLength[block_count] = blockLength[block_count] + 1;
            } else {
                block_count++;
                blockLength[block_count] = 1;
                blockType[block_count] = generalFieldType[i];
            }
        }

        block_count++;

        for (i = 0; i < block_count; i++) {
            current = blockType[i];
            next = blockType[i + 1];

            if ((current == EncodeMode.ISOIEC) && (i != (block_count - 1))) {
                if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] >= 4)) {
                    blockType[i + 1] = EncodeMode.NUMERIC;
                }
                if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] < 4)) {
                    blockType[i + 1] = EncodeMode.ISOIEC;
                }
                if ((next == EncodeMode.ALPHA_OR_ISO) && (blockLength[i + 1] >= 5)) {
                    blockType[i + 1] = EncodeMode.ALPHA;
                }
                if ((next == EncodeMode.ALPHA_OR_ISO) && (blockLength[i + 1] < 5)) {
                    blockType[i + 1] = EncodeMode.ISOIEC;
                }
            }

            if (current == EncodeMode.ALPHA_OR_ISO) {
                blockType[i] = EncodeMode.ALPHA;
                current = EncodeMode.ALPHA;
            }

            if ((current == EncodeMode.ALPHA) && (i != (block_count - 1))) {
                if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] >= 6)) {
                    blockType[i + 1] = EncodeMode.NUMERIC;
                }
                if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] < 6)) {
                    if ((i == block_count - 2) && (blockLength[i + 1] >= 4)) {
                        blockType[i + 1] = EncodeMode.NUMERIC;
                    } else {
                        blockType[i + 1] = EncodeMode.ALPHA;
                    }
                }
            }

            if (current == EncodeMode.ANY_ENC) {
                blockType[i] = EncodeMode.NUMERIC;
            }
        }

        if (block_count > 1) {
            i = 1;
            while (i < block_count) {
                if (blockType[i - 1] == blockType[i]) {
                    /* bring together */
                    blockLength[i - 1] = blockLength[i - 1] + blockLength[i];
                    j = i + 1;

                    /* decrease the list */
                    while (j < block_count) {
                        blockLength[j - 1] = blockLength[j];
                        blockType[j - 1] = blockType[j];
                        j++;
                    }
                    block_count--;
                    i--;
                }
                i++;
            }
        }

        for (i = 0; i < block_count - 1; i++) {
            if (blockType[i] == EncodeMode.NUMERIC && (blockLength[i] & 1) != 0) {
                /* Odd size numeric block */
                blockLength[i] = blockLength[i] - 1;
                blockLength[i + 1] = blockLength[i + 1] + 1;
            }
        }

        j = 0;
        for (i = 0; i < block_count; i++) {
            for (k = 0; k < blockLength[i]; k++) {
                generalFieldType[j] = blockType[i];
                j++;
            }
        }

        if (blockType[block_count - 1] == EncodeMode.NUMERIC && (blockLength[block_count - 1] & 1) != 0) {
            /* If the last block is numeric and an odd size, further processing needs to be done outside this procedure */
            return true;
        } else {
            return false;
        }
    }

    private static int parseInt(int[] chars, int index, int length) {
        int val = 0;
        int pow = (int) Math.pow(10, length - 1);
        for (int i = 0; i < length; i++) {
            int c = chars[index + i];
            val += (c - '0') * pow;
            pow /= 10;
        }
        return val;
    }
} public class DataBarLimited extends Symbol {

    private static final int[] T_EVEN_LTD = {
        28, 728, 6454, 203, 2408, 1, 16632
    };

    private static final int[] MODULES_ODD_LTD = {
        17, 13, 9, 15, 11, 19, 7
    };

    private static final int[] MODULES_EVEN_LTD = {
        9, 13, 17, 11, 15, 7, 19
    };

    private static final int[] WIDEST_ODD_LTD = {
        6, 5, 3, 5, 4, 8, 1
    };

    private static final int[] WIDEST_EVEN_LTD = {
        3, 4, 6, 4, 5, 1, 8
    };

    private static final int[] CHECKSUM_WEIGHT_LTD = { /* Table 7 */
        1, 3, 9, 27, 81, 65, 17, 51, 64, 14, 42, 37, 22, 66,
        20, 60, 2, 6, 18, 54, 73, 41, 34, 13, 39, 28, 84, 74
    };

    private static final int[] FINDER_PATTERN_LTD = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1,
        1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1,
        1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 1, 1, 1,
        1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 3, 1, 1, 1,
        1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1,
        1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1,
        1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1,
        1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1,
        1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1,
        1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1,
        1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 2, 1, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 2, 1, 1, 1,
        1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1,
        1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1, 1,
        1, 2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1,
        1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1,
        1, 3, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1,
        1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1,
        1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 1, 1,
        1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1,
        1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 2, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1,
        1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1,
        1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1,
        1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1,
        1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1,
        1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1,
        1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1,
        1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1,
        1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1,
        1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1,
        1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1,
        1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1, 1,
        1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 1,
        1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1, 1,
        1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1,
        1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1,
        1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1,
        1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1,
        1, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1,
        1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1,
        1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1,
        1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1,
        1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1,
        1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1,
        1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1,
        1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1,
        1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1,
        1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1,
        1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1,
        1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1,
        2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1,
        2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1,
        2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1,
        2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1,
        2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1,
        2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1,
        2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1,
        2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1,
        2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1 /* ISO/IEC 24724-2011 57 */
    };

    private boolean linkageFlag;

    /**
     * Although this is a GS1 symbology, input data is expected to omit the [01] Application Identifier,
     * as well as the check digit. Thus, the input data is not considered GS1-format data.
     */
    @Override
    protected boolean gs1Supported() {
        return false;
    }

    protected void setLinkageFlag() {
        linkageFlag = true;
    }

    protected void unsetLinkageFlag() {
        linkageFlag = false;
    }

    @Override
    protected void encode() {
        BigInteger accum;
        BigInteger left_reg;
        BigInteger right_reg;
        int left_group;
        int right_group;
        int i, j;
        int left_character;
        int right_character;
        int left_odd;
        int right_odd;
        int left_even;
        int right_even;
        int[] left_widths = new int[14];
        int[] right_widths = new int[14];
        int checksum;
        int[] check_elements = new int[14];
        int[] total_widths = new int[46];
        boolean bar_latch;
        int writer;
        int check_digit = 0;
        int count = 0;
        String hrt;
        int compositeOffset = 0;

        if (content.length() > 13) {
            throw new OkapiException("Input too long");
        }

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

        if (content.length() == 13 && content.charAt(0) != '0' && content.charAt(0) != '1') {
            throw new OkapiException("Input out of range");
        }

        accum = new BigInteger(content);

        if (linkageFlag) {
            /* Add symbol linkage flag */
            accum = accum.add(new BigInteger("2015133531096"));
        }

        /* Calculate left and right pair values */
        left_reg = accum.divide(new BigInteger("2013571"));
        right_reg = accum.mod(new BigInteger("2013571"));

        left_group = 0;
        if (left_reg.compareTo(new BigInteger("183063")) == 1) {
            left_group = 1;
        }
        if (left_reg.compareTo(new BigInteger("820063")) == 1) {
            left_group = 2;
        }
        if (left_reg.compareTo(new BigInteger("1000775")) == 1) {
            left_group = 3;
        }
        if (left_reg.compareTo(new BigInteger("1491020")) == 1) {
            left_group = 4;
        }
        if (left_reg.compareTo(new BigInteger("1979844")) == 1) {
            left_group = 5;
        }
        if (left_reg.compareTo(new BigInteger("1996938")) == 1) {
            left_group = 6;
        }

        right_group = 0;
        if (right_reg.compareTo(new BigInteger("183063")) == 1) {
            right_group = 1;
        }
        if (right_reg.compareTo(new BigInteger("820063")) == 1) {
            right_group = 2;
        }
        if (right_reg.compareTo(new BigInteger("1000775")) == 1) {
            right_group = 3;
        }
        if (right_reg.compareTo(new BigInteger("1491020")) == 1) {
            right_group = 4;
        }
        if (right_reg.compareTo(new BigInteger("1979844")) == 1) {
            right_group = 5;
        }
        if (right_reg.compareTo(new BigInteger("1996938")) == 1) {
            right_group = 6;
        }

        infoLine("Data Characters: " + (left_group + 1) + " " + (right_group + 1));

        switch(left_group) {
            case 1:
                left_reg = left_reg.subtract(new BigInteger("183064"));
                break;
            case 2:
                left_reg = left_reg.subtract(new BigInteger("820064"));
                break;
            case 3:
                left_reg = left_reg.subtract(new BigInteger("1000776"));
                break;
            case 4:
                left_reg = left_reg.subtract(new BigInteger("1491021"));
                break;
            case 5:
                left_reg = left_reg.subtract(new BigInteger("1979845"));
                break;
            case 6:
                left_reg = left_reg.subtract(new BigInteger("1996939"));
                break;
        }

        switch(right_group) {
            case 1:
                right_reg = right_reg.subtract(new BigInteger("183064"));
                break;
            case 2:
                right_reg = right_reg.subtract(new BigInteger("820064"));
                break;
            case 3:
                right_reg = right_reg.subtract(new BigInteger("1000776"));
                break;
            case 4:
                right_reg = right_reg.subtract(new BigInteger("1491021"));
                break;
            case 5:
                right_reg = right_reg.subtract(new BigInteger("1979845"));
                break;
            case 6:
                right_reg = right_reg.subtract(new BigInteger("1996939"));
                break;
        }

        left_character = left_reg.intValue();
        right_character = right_reg.intValue();

        left_odd = left_character / T_EVEN_LTD[left_group];
        left_even = left_character % T_EVEN_LTD[left_group];
        right_odd = right_character / T_EVEN_LTD[right_group];
        right_even = right_character % T_EVEN_LTD[right_group];

        int[] widths = getWidths(left_odd, MODULES_ODD_LTD[left_group], 7, WIDEST_ODD_LTD[left_group], 1);
        left_widths[0] = widths[0];
        left_widths[2] = widths[1];
        left_widths[4] = widths[2];
        left_widths[6] = widths[3];
        left_widths[8] = widths[4];
        left_widths[10] = widths[5];
        left_widths[12] = widths[6];

        widths = getWidths(left_even, MODULES_EVEN_LTD[left_group], 7, WIDEST_EVEN_LTD[left_group], 0);
        left_widths[1] = widths[0];
        left_widths[3] = widths[1];
        left_widths[5] = widths[2];
        left_widths[7] = widths[3];
        left_widths[9] = widths[4];
        left_widths[11] = widths[5];
        left_widths[13] = widths[6];

        widths = getWidths(right_odd, MODULES_ODD_LTD[right_group], 7, WIDEST_ODD_LTD[right_group], 1);
        right_widths[0] = widths[0];
        right_widths[2] = widths[1];
        right_widths[4] = widths[2];
        right_widths[6] = widths[3];
        right_widths[8] = widths[4];
        right_widths[10] = widths[5];
        right_widths[12] = widths[6];

        widths = getWidths(right_even, MODULES_EVEN_LTD[right_group], 7, WIDEST_EVEN_LTD[right_group], 0);
        right_widths[1] = widths[0];
        right_widths[3] = widths[1];
        right_widths[5] = widths[2];
        right_widths[7] = widths[3];
        right_widths[9] = widths[4];
        right_widths[11] = widths[5];
        right_widths[13] = widths[6];

        checksum = 0;
        /* Calculate the checksum */
        for(i = 0; i < 14; i++) {
                checksum += CHECKSUM_WEIGHT_LTD[i] * left_widths[i];
                checksum += CHECKSUM_WEIGHT_LTD[i + 14] * right_widths[i];
        }
        checksum %= 89;

        infoLine("Checksum: " + checksum);

        for(i = 0; i < 14; i++) {
                check_elements[i] = FINDER_PATTERN_LTD[i + (checksum * 14)];
        }

        total_widths[0] = 1;
        total_widths[1] = 1;
        total_widths[44] = 1;
        total_widths[45] = 1;
        for(i = 0; i < 14; i++) {
                total_widths[i + 2] = left_widths[i];
                total_widths[i + 16] = check_elements[i];
                total_widths[i + 30] = right_widths[i];
        }

        StringBuilder bin = new StringBuilder();
        StringBuilder notbin = new StringBuilder();
        writer = 0;
        bar_latch = false;
        for (i = 0; i < 46; i++) {
            for (j = 0; j < total_widths[i]; j++) {
                if (bar_latch) {
                    bin.append('1');
                    notbin.append('0');
                } else {
                    bin.append('0');
                    notbin.append('1');
                }
                writer++;
            }
            if (bar_latch) {
                bar_latch = false;
            } else {
                bar_latch = true;
            }
        }

        /* Calculate check digit from Annex A and place human readable text */

        readable = "(01)";
        hrt = "";
        for (i = content.length(); i < 13; i++) {
            hrt += "0";
        }
        hrt += content;

        for (i = 0; i < 13; i++) {
            count += (hrt.charAt(i) - '0');
            if ((i & 1) == 0) {
                count += 2 * (hrt.charAt(i) - '0');
            }
        }

        check_digit = 10 - (count % 10);
        if (check_digit == 10) {
            check_digit = 0;
        }

        hrt += (char) (check_digit + '0');
        readable += hrt;

        if (linkageFlag) {
            compositeOffset = 1;
        }

        row_count = 1 + compositeOffset;
        row_height = new int[1 + compositeOffset];
        row_height[0 + compositeOffset] = -1;
        pattern = new String[1 + compositeOffset];
        pattern[0 + compositeOffset] = bin2pat(bin);

        if (linkageFlag) {
            // Add composite symbol separator
            notbin.delete(70, notbin.length());
            notbin.delete(0, 4);
            row_height[0] = 1;
            pattern[0] = "04" + bin2pat(notbin);
        }
    }

    private static int getCombinations(int n, int r) {

        int i, j;
        int maxDenom, minDenom;
        int val;

        if (n - r > r) {
            minDenom = r;
            maxDenom = n - r;
        } else {
            minDenom = n - r;
            maxDenom = r;
        }

        val = 1;
        j = 1;

        for (i = n; i > maxDenom; i--) {
            val *= i;
            if (j <= minDenom) {
                val /= j;
                j++;
            }
        }

        for (; j <= minDenom; j++) {
            val /= j;
        }

        return val;
    }

    static int[] getWidths(int val, int n, int elements, int maxWidth, int noNarrow) {

        int bar;
        int elmWidth;
        int mxwElement;
        int subVal, lessVal;
        int narrowMask = 0;
        int[] widths = new int[elements];

        for (bar = 0; bar < elements - 1; bar++) {
            for (elmWidth = 1, narrowMask |= (1 << bar); ;
                    elmWidth++, narrowMask &= ~ (1 << bar)) {
                /* get all combinations */
                subVal = getCombinations(n - elmWidth - 1, elements - bar - 2);
                /* less combinations with no single-module element */
                if ((noNarrow == 0) && (narrowMask == 0)
                        && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) {
                    subVal -= getCombinations(n - elmWidth - (elements - bar), elements - bar - 2);
                }
                /* less combinations with elements > maxVal */
                if (elements - bar - 1 > 1) {
                    lessVal = 0;
                    for (mxwElement = n - elmWidth - (elements - bar - 2);
                    mxwElement > maxWidth;
                    mxwElement--) {
                        lessVal += getCombinations(n - elmWidth - mxwElement - 1, elements - bar - 3);
                    }
                    subVal -= lessVal * (elements - 1 - bar);
                } else if (n - elmWidth > maxWidth) {
                    subVal--;
                }
                val -= subVal;
                if (val < 0) break;
            }
            val += subVal;
            n -= elmWidth;
            widths[bar] = elmWidth;
        }

        widths[bar] = n;

        return widths;
    }
} public class DataMatrix extends Symbol {

    public enum ForceMode {
        NONE, SQUARE, RECTANGULAR
    }

    private enum Mode {
        NULL, DM_ASCII, DM_C40, DM_TEXT, DM_X12, DM_EDIFACT, DM_BASE256
    }

    private static final int[] C40_SHIFT = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3
    };

    private static final int[] C40_VALUE = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6,
        7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16,
        17, 18, 19, 20, 21, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
        27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 22, 23, 24, 25, 26,
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
    };

    private static final int[] TEXT_SHIFT = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2,
        3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 3, 3, 3, 3, 3
    };

    private static final int[] TEXT_VALUE = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6,
        7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16,
        17, 18, 19, 20, 21, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 22, 23, 24, 25, 26, 0, 14,
        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
        33, 34, 35, 36, 37, 38, 39, 27, 28, 29, 30, 31
    };

    private static final int[] INT_SYMBOL = {
        0, 1, 3, 5, 7, 8, 10, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
        25, 26, 27, 28, 29, 2, 4, 6, 9, 11, 14
    };

    private static final int[] MATRIX_H = {
        10, 12, 8, 14, 8, 16, 12, 18, 20, 12, 22, 16, 24, 26, 16, 32, 36, 40,
        44, 48, 52, 64, 72, 80, 88, 96, 104, 120, 132, 144
    };

    private static final int[] MATRIX_W = {
        10, 12, 18, 14, 32, 16, 26, 18, 20, 36, 22, 36, 24, 26, 48, 32, 36, 40,
        44, 48, 52, 64, 72, 80, 88, 96, 104, 120, 132, 144
    };

    private static final int[] MATRIX_FH = {
        10, 12, 8, 14, 8, 16, 12, 18, 20, 12, 22, 16, 24, 26, 16, 16, 18, 20,
        22, 24, 26, 16, 18, 20, 22, 24, 26, 20, 22, 24
    };

    private static final int[] MATRIX_FW = {
        10, 12, 18, 14, 16, 16, 26, 18, 20, 18, 22, 18, 24, 26, 24, 16, 18, 20,
        22, 24, 26, 16, 18, 20, 22, 24, 26, 20, 22, 24
    };

    private static final int[] MATRIX_BYTES = {
        3, 5, 5, 8, 10, 12, 16, 18, 22, 22, 30, 32, 36, 44, 49, 62, 86, 114,
        144, 174, 204, 280, 368, 456, 576, 696, 816, 1050, 1304, 1558
    };

    private static final int[] MATRIX_DATA_BLOCK = {
        3, 5, 5, 8, 10, 12, 16, 18, 22, 22, 30, 32, 36, 44, 49, 62, 86, 114,
        144, 174, 102, 140, 92, 114, 144, 174, 136, 175, 163, 156
    };

    private static final int[] MATRIX_RS_BLOCK = {
        5, 7, 7, 10, 11, 12, 14, 14, 18, 18, 20, 24, 24, 28, 28, 36, 42, 48, 56,
        68, 42, 56, 36, 48, 56, 68, 56, 68, 62, 62
    };

    private static final int DM_SIZES_COUNT = MATRIX_H.length;

    // user-specified values and settings

    private ForceMode forceMode = ForceMode.NONE;
    private int preferredSize;
    private int structuredAppendFileId = 1;
    private int structuredAppendPosition = 1;
    private int structuredAppendTotal = 1;

    // internal state calculated when setContent() is called

    private int actualSize = -1;
    private int[] target = new int[2200];
    private int[] binary = new int[2200];
    private int binary_length;
    private Mode last_mode;
    private int[] places;
    private int process_p;
    private int[] process_buffer = new int[8];
    private int codewordCount;

    /**
     * Forces the symbol to be either square or rectangular (non-square).
     *
     * @param forceMode the force mode to use
     */
    public void setForceMode(ForceMode forceMode) {
        this.forceMode = forceMode;
    }

    /**
     * Returns the force mode used by this symbol.
     *
     * @return the force mode used by this symbol
     */
    public ForceMode getForceMode() {
        return forceMode;
    }

    /**
     * Sets the preferred symbol size according to the values in the following
     * table. Values may be ignored if the data is too big to fit in the
     * specified symbol, or if {@link #setForceMode(ForceMode)} has been invoked.
     *
     * <table>
     * <tbody>
     * <tr><th>Input</th><th>Symbol Size</th><th>Input</th><th>Symbol Size</th></tr>
     * <tr><td>1    </td><td>10 x 10    </td><td>16</td><td>64 x 64       </td></tr>
     * <tr><td>2    </td><td>12 x 12    </td><td>17</td><td>72 x 72       </td></tr>
     * <tr><td>3    </td><td>14 x 14    </td><td>18</td><td>80 x 80       </td></tr>
     * <tr><td>4    </td><td>16 x 16    </td><td>19</td><td>88 x 88       </td></tr>
     * <tr><td>5    </td><td>18 x 18    </td><td>20</td><td>96 x 96       </td></tr>
     * <tr><td>6    </td><td>20 x 20    </td><td>21</td><td>104 x 104     </td></tr>
     * <tr><td>7    </td><td>22 x 22    </td><td>22</td><td>120 x 120     </td></tr>
     * <tr><td>8    </td><td>24 x 24    </td><td>23</td><td>132 x 132     </td></tr>
     * <tr><td>9    </td><td>26 x 26    </td><td>24</td><td>144 x 144     </td></tr>
     * <tr><td>10   </td><td>32 x 32    </td><td>25</td><td>8 x 18        </td></tr>
     * <tr><td>11   </td><td>36 x 36    </td><td>26</td><td>8 x 32        </td></tr>
     * <tr><td>12   </td><td>40 x 40    </td><td>27</td><td>12 x 26       </td></tr>
     * <tr><td>13   </td><td>44 x 44    </td><td>28</td><td>12 x 36       </td></tr>
     * <tr><td>14   </td><td>48 x 48    </td><td>29</td><td>16 x 36       </td></tr>
     * <tr><td>15   </td><td>52 x 52    </td><td>30</td><td>16 x 48       </td></tr>
     * </tbody>
     * </table>
     *
     * @param size the symbol size to use (1 - 30 inclusive)
     */
    public void setPreferredSize(int size) {
        preferredSize = size;
    }

    /**
     * Returns the preferred symbol size.
     *
     * @return the preferred symbol size
     * @see #setPreferredSize(int)
     */
    public int getPreferredSize() {
        return preferredSize;
    }

    /**
     * Returns the actual symbol size used. Available after the symbol is encoded.
     *
     * @return the actual symbol size used
     */
    public int getActualSize() {
        if (actualSize != -1) {
            return actualSize;
        } else {
            throw new IllegalStateException("Actual size not calculated until symbol is encoded.");
        }
    }

    /**
     * Returns the actual width (columns) used for the symbol. Available after the symbol is encoded.
     *
     * @return the actual width (columns) used for the symbol
     */
    public int getActualWidth() {
        int index1 = getActualSize() - 1;
        int index2 = INT_SYMBOL[index1];
        return MATRIX_W[index2];
    }

    /**
     * Returns the actual height (rows) used for the symbol. Available after the symbol is encoded.
     *
     * @return the actual height (rows) used for the symbol
     */
    public int getActualHeight() {
        int index1 = getActualSize() - 1;
        int index2 = INT_SYMBOL[index1];
        return MATRIX_H[index2];
    }

    /**
     * If this Data Matrix symbol is part of a series of Data Matrix symbols appended in a structured
     * format, this method sets the position of this symbol in the series. Valid values are 1 through
     * 16 inclusive.
     *
     * @param position the position of this Data Matrix symbol in the structured append series
     */
    public void setStructuredAppendPosition(int position) {
        if (position < 1 || position > 16) {
            throw new IllegalArgumentException("Invalid Data Matrix structured append position: " + position);
        }
        this.structuredAppendPosition = position;
    }

    /**
     * Returns the position of this Data Matrix symbol in a series of symbols using structured append.
     * If this symbol is not part of such a series, this method will return <code>1</code>.
     *
     * @return the position of this Data Matrix symbol in a series of symbols using structured append
     */
    public int getStructuredAppendPosition() {
        return structuredAppendPosition;
    }

    /**
     * If this Data Matrix symbol is part of a series of Data Matrix symbols appended in a structured
     * format, this method sets the total number of symbols in the series. Valid values are
     * 1 through 16 inclusive. A value of 1 indicates that this symbol is not part of a structured
     * append series.
     *
     * @param total the total number of Data Matrix symbols in the structured append series
     */
    public void setStructuredAppendTotal(int total) {
        if (total < 1 || total > 16) {
            throw new IllegalArgumentException("Invalid Data Matrix structured append total: " + total);
        }
        this.structuredAppendTotal = total;
    }

    /**
     * Returns the size of the series of Data Matrix symbols using structured append that this symbol
     * is part of. If this symbol is not part of a structured append series, this method will return
     * <code>1</code>.
     *
     * @return size of the series that this symbol is part of
     */
    public int getStructuredAppendTotal() {
        return structuredAppendTotal;
    }

    /**
     * If this Data Matrix symbol is part of a series of Data Matrix symbols appended in a structured format,
     * this method sets the unique file ID for the series. Valid values are 1 through 64,516 inclusive.
     *
     * @param fileId the unique file ID for the series that this symbol is part of
     */
    public void setStructuredAppendFileId(int fileId) {
        if (fileId < 1 || fileId > 64_516) {
            throw new IllegalArgumentException("Invalid Data Matrix structured append file ID: " + fileId);
        }
        this.structuredAppendFileId = fileId;
    }

    /**
     * Returns the unique file ID of the series of Data Matrix symbols using structured append that this
     * symbol is part of. If this symbol is not part of a structured append series, this method will return
     * <code>1</code>.
     *
     * @return the unique file ID for the series that this symbol is part of
     */
    public int getStructuredAppendFileId() {
        return structuredAppendFileId;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {

        int i, binlen, skew = 0;
        int symbolsize, optionsize, calcsize;
        int taillength;
        int H, W, FH, FW, datablock, bytes, rsblock;
        int x, y, NC, NR, v;
        int[] grid;
        StringBuilder bin = new StringBuilder();

        eciProcess(); // Get ECI mode

        binlen = generateCodewords();

        if (preferredSize >= 1 && preferredSize <= DM_SIZES_COUNT) {
            optionsize = INT_SYMBOL[preferredSize - 1];
        } else {
            optionsize = -1;
        }

        calcsize = DM_SIZES_COUNT - 1;
        for (i = DM_SIZES_COUNT - 1; i > -1; i--) {
            if (MATRIX_BYTES[i] >= (binlen + process_p)) {
                calcsize = i;
            }
        }

        if (optionsize == -1) {
            // We are in automatic size mode as the exact symbol size was not given
            // Now check the detailed search options square only or rectangular only
            if (forceMode == ForceMode.SQUARE) {
                /* Skip rectangular symbols in square only mode */
                while (calcsize < DM_SIZES_COUNT && MATRIX_H[calcsize] != MATRIX_W[calcsize]) {
                    calcsize++;
                }
            } else if (forceMode == ForceMode.RECTANGULAR) {
                /* Skip square symbols in rectangular only mode */
                while (calcsize < DM_SIZES_COUNT && MATRIX_H[calcsize] == MATRIX_W[calcsize]) {
                    calcsize++;
                }
            }
            if (calcsize >= DM_SIZES_COUNT) {
                throw new OkapiException("Data too long to fit in symbol");
            }
            symbolsize = calcsize;
        } else {
            // The symbol size was specified by the user
            // Thus check if the data fits into this symbol size and use this size
            if (calcsize > optionsize) {
                throw new OkapiException("Input too long for selected symbol size");
            }
            symbolsize = optionsize;
        }

        // Now we know the symbol size we can handle the remaining data in the process buffer.
        int symbolsLeft = MATRIX_BYTES[symbolsize] - binlen;
        binlen = encodeRemainder(symbolsLeft, binlen);

        if (binlen > MATRIX_BYTES[symbolsize]) {
            throw new OkapiException("Data too long to fit in symbol");
        }

        H = MATRIX_H[symbolsize];
        W = MATRIX_W[symbolsize];
        FH = MATRIX_FH[symbolsize];
        FW = MATRIX_FW[symbolsize];
        bytes = MATRIX_BYTES[symbolsize];
        datablock = MATRIX_DATA_BLOCK[symbolsize];
        rsblock = MATRIX_RS_BLOCK[symbolsize];

        codewordCount = datablock + rsblock; // data codewords + error correction codewords

        taillength = bytes - binlen;

        if (taillength != 0) {
            addPadBits(binlen, taillength);
        }

        // ecc code
        if (symbolsize == 29) {
            skew = 1;
        }
        calculateErrorCorrection(bytes, datablock, rsblock, skew);
        NC = W - 2 * (W / FW);
        NR = H - 2 * (H / FH);
        places = new int[NC * NR];
        placeData(NR, NC);
        grid = new int[W * H];
        for (i = 0; i < (W * H); i++) {
            grid[i] = 0;
        }
        for (y = 0; y < H; y += FH) {
            for (x = 0; x < W; x++) {
                grid[y * W + x] = 1;
            }
            for (x = 0; x < W; x += 2) {
                grid[(y + FH - 1) * W + x] = 1;
            }
        }
        for (x = 0; x < W; x += FW) {
            for (y = 0; y < H; y++) {
                grid[y * W + x] = 1;
            }
            for (y = 0; y < H; y += 2) {
                grid[y * W + x + FW - 1] = 1;
            }
        }
        for (y = 0; y < NR; y++) {
            for (x = 0; x < NC; x++) {
                v = places[(NR - y - 1) * NC + x];
                if (v == 1 || (v > 7 && (target[(v >> 3) - 1] & (1 << (v & 7))) != 0)) {
                    grid[(1 + y + 2 * (y / (FH - 2))) * W + 1 + x + 2 * (x / (FW - 2))] = 1;
                }
            }
        }

        actualSize = positionOf(symbolsize, INT_SYMBOL) + 1;
        readable = "";
        pattern = new String[H];
        row_count = H;
        row_height = new int[H];
        for (y = H - 1; y >= 0; y--) {
            bin.setLength(0);
            for (x = 0; x < W; x++) {
                if (grid[W * y + x] == 1) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            pattern[(H - y) - 1] = bin2pat(bin);
            row_height[(H - y) - 1] = moduleWidth;
        }

        infoLine("Grid Size: " + W + " X " + H);
        infoLine("Data Codewords: " + datablock);
        infoLine("ECC Codewords: " + rsblock);
    }

    @Override
    protected int[] getCodewords() {
        return Arrays.copyOf(target, codewordCount);
    }

    private int generateCodewords() {
        /* Encodes data using ASCII, C40, Text, X12, EDIFACT or Base 256 modes as appropriate */
        /* Supports encoding FNC1 in supporting systems */
        /* Supports ECI encoding for whole message only, not inline switching */

        info("Encoding: ");
        int sp, tp, i;
        Mode current_mode, next_mode;
        int inputlen = inputData.length;

        sp = 0;
        tp = 0;
        process_p = 0;

        for (i = 0; i < 8; i++) {
            process_buffer[i] = 0;
        }
        binary_length = 0;

        /* step (a) */
        current_mode = Mode.DM_ASCII;
        next_mode = Mode.DM_ASCII;

        if (structuredAppendTotal != 1) {

            /* FNC2 */
            target[tp] = 233;
            tp++;
            binary[binary_length] = ' ';
            binary_length++;
            info("FNC2 ");

            /* symbol sequence indicator (position + total) */
            int ssi = ((structuredAppendPosition - 1) << 4) | (17 - structuredAppendTotal);
            target[tp] = ssi;
            tp++;
            binary[binary_length] = ' ';
            binary_length++;
            infoSpace(ssi);

            /* file identification codeword 1 (valid values 1 - 254) */
            int id1 = 1 + ((structuredAppendFileId - 1) / 254);
            target[tp] = id1;
            tp++;
            binary[binary_length] = ' ';
            binary_length++;
            infoSpace(id1);

            /* file identification codeword 2 (valid values 1 - 254) */
            int id2 = 1 + ((structuredAppendFileId - 1) % 254);
            target[tp] = id2;
            tp++;
            binary[binary_length] = ' ';
            binary_length++;
            infoSpace(id2);
        }

        if (inputDataType == DataType.GS1) {
            target[tp] = 232;
            tp++;
            binary[binary_length] = ' ';
            binary_length++;
            info("FNC1 ");
        } /* FNC1 */

        if (readerInit) {
            if (inputDataType == DataType.GS1) {
                throw new OkapiException("Cannot encode in GS1 mode and Reader Initialisation at the same time");
            } else {
                target[tp] = 234; /* FNC3 */
                tp++; /* Reader Programming */
                binary[binary_length] = ' ';
                binary_length++;
                info("RP ");
            }
        }

        if (eciMode != 3) {
            target[tp] = 241; // ECI
            tp++;
            binary[binary_length] = ' ';
            binary_length++;
            if (eciMode <= 126) {
                target[tp] = eciMode + 1;
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
            }
            if ((eciMode >= 127) && (eciMode <= 16382)) {
                target[tp] = ((eciMode - 127) / 254) + 128;
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
                target[tp] = ((eciMode - 127) % 254) + 1;
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
            }
            if (eciMode >= 16383) {
                target[tp] = ((eciMode - 16383) / 64516) + 192;
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
                target[tp] = (((eciMode - 16383) / 254) % 254) + 1;
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
                target[tp] = ((eciMode - 16383) % 254) + 1;
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
            }
            info("ECI " + eciMode + " ");
        }

        /* Check for Macro05/Macro06 */
        /* "[)>[RS]05[GS]...[RS][EOT]" -> CW 236 */
        /* "[)>[RS]06[GS]...[RS][EOT]" -> CW 237 */
        if (tp == 0 & sp == 0 && inputlen >= 9) {
            if (inputData[0] == '[' && inputData[1] == ')' && inputData[2] == '>'
                    && inputData[3] == '\u001e' && inputData[4] == '0'
                    && (inputData[5] == '5' || inputData[5] == '6')
                    && inputData[6] == '\u001d'
                    && inputData[inputlen - 2] == '\u001e'
                    && inputData[inputlen - 1] == '\u0004') {
                /* Output macro codeword */
                if (inputData[5] == '5') {
                    target[tp] = 236;
                    info("Macro05 ");
                } else {
                    target[tp] = 237;
                    info("Macro06 ");
                }
                tp++;
                binary[binary_length] = ' ';
                binary_length++;
                /* Remove macro characters from input string */
                sp = 7;
                inputlen -= 2;
                inputData = Arrays.copyOf(inputData, inputData.length - 2);
            }
        }

        while (sp < inputlen) {

            current_mode = next_mode;

            /* step (b) - ASCII encodation */
            if (current_mode == Mode.DM_ASCII) {
                next_mode = Mode.DM_ASCII;

                for (i = 0; i < 8; i++) {
                    process_buffer[i] = 0;
                }

                if (isTwoDigits(sp)) {
                    target[tp] = (10 * Character.getNumericValue(inputData[sp]))
                            + Character.getNumericValue(inputData[sp + 1]) + 130;
                    infoSpace(target[tp] - 130);
                    tp++;
                    binary[binary_length] = ' ';
                    binary_length++;
                    sp += 2;
                } else {
                    next_mode = lookAheadTest(sp, current_mode);

                    if (next_mode != Mode.DM_ASCII) {
                        switch (next_mode) {
                            case DM_C40:
                                target[tp] = 230;
                                tp++;
                                binary[binary_length] = ' ';
                                binary_length++;
                                info("C40 ");
                                break;
                            case DM_TEXT:
                                target[tp] = 239;
                                tp++;
                                binary[binary_length] = ' ';
                                binary_length++;
                                info("TEX ");
                                break;
                            case DM_X12:
                                target[tp] = 238;
                                tp++;
                                binary[binary_length] = ' ';
                                binary_length++;
                                info("X12 ");
                                break;
                            case DM_EDIFACT:
                                target[tp] = 240;
                                tp++;
                                binary[binary_length] = ' ';
                                binary_length++;
                                info("EDI ");
                                break;
                            case DM_BASE256:
                                target[tp] = 231;
                                tp++;
                                binary[binary_length] = ' ';
                                binary_length++;
                                info("BAS ");
                                break;
                        }
                    } else {
                        if (inputData[sp] > 127) {
                            target[tp] = 235; /* FNC4 */

                            info("FNC4 ");
                            tp++;
                            target[tp] = (inputData[sp] - 128) + 1;
                            infoSpace(target[tp] - 1);
                            tp++;
                            binary[binary_length] = ' ';
                            binary_length++;
                            binary[binary_length] = ' ';
                            binary_length++;
                        } else {
                            if (inputData[sp] == FNC1) {
                                target[tp] = 232; /* FNC1 */
                                info("FNC1 ");
                            } else {
                                target[tp] = inputData[sp] + 1;
                                infoSpace(target[tp] - 1);
                            }
                            tp++;
                            binary[binary_length] = ' ';
                            binary_length++;
                        }
                        sp++;
                    }
                }
            }

            /* step (c) C40 encodation */
            if (current_mode == Mode.DM_C40) {
                int shift_set, value;

                next_mode = Mode.DM_C40;
                if (process_p == 0) {
                    next_mode = lookAheadTest(sp, current_mode);
                }

                if (next_mode != Mode.DM_C40) {
                    target[tp] = 254;
                    tp++;
                    binary[binary_length] = ' ';
                    binary_length++; /* Unlatch */

                    next_mode = Mode.DM_ASCII;
                    info("ASC ");
                } else {
                    if (inputData[sp] == FNC1) {
                        shift_set = 2;
                        value = 27; /* FNC1 */
                    } else if (inputData[sp] > 127) {
                        process_buffer[process_p] = 1;
                        process_p++;
                        process_buffer[process_p] = 30;
                        process_p++; /* Upper Shift */

                        shift_set = C40_SHIFT[inputData[sp] - 128];
                        value = C40_VALUE[inputData[sp] - 128];
                    } else {
                        shift_set = C40_SHIFT[inputData[sp]];
                        value = C40_VALUE[inputData[sp]];
                    }

                    if (shift_set != 0) {
                        process_buffer[process_p] = shift_set - 1;
                        process_p++;
                    }
                    process_buffer[process_p] = value;
                    process_p++;

                    while (process_p >= 3) {
                        int iv;

                        iv = (1600 * process_buffer[0]) + (40 * process_buffer[1])
                                + (process_buffer[2]) + 1;
                        target[tp] = iv / 256;
                        tp++;
                        target[tp] = iv % 256;
                        tp++;
                        binary[binary_length] = ' ';
                        binary_length++;
                        binary[binary_length] = ' ';
                        binary_length++;
                        info("(" + process_buffer[0] + " " + process_buffer[1] + " " + process_buffer[2] + ") ");

                        process_buffer[0] = process_buffer[3];
                        process_buffer[1] = process_buffer[4];
                        process_buffer[2] = process_buffer[5];
                        process_buffer[3] = 0;
                        process_buffer[4] = 0;
                        process_buffer[5] = 0;
                        process_p -= 3;
                    }
                    sp++;
                }
            }

            /* step (d) Text encodation */
            if (current_mode == Mode.DM_TEXT) {
                int shift_set, value;

                next_mode = Mode.DM_TEXT;
                if (process_p == 0) {
                    next_mode = lookAheadTest(sp, current_mode);
                }

                if (next_mode != Mode.DM_TEXT) {
                    target[tp] = 254;
                    tp++;
                    binary[binary_length] = ' ';
                    binary_length++; /* Unlatch */

                    next_mode = Mode.DM_ASCII;
                    info("ASC ");
                } else {
                    if (inputData[sp] == FNC1) {
                        shift_set = 2;
                        value = 27; /* FNC1 */
                    } else if (inputData[sp] > 127) {
                        process_buffer[process_p] = 1;
                        process_p++;
                        process_buffer[process_p] = 30;
                        process_p++; /* Upper Shift */

                        shift_set = TEXT_SHIFT[inputData[sp] - 128];
                        value = TEXT_VALUE[inputData[sp] - 128];
                    } else {
                        shift_set = TEXT_SHIFT[inputData[sp]];
                        value = TEXT_VALUE[inputData[sp]];
                    }

                    if (shift_set != 0) {
                        process_buffer[process_p] = shift_set - 1;
                        process_p++;
                    }
                    process_buffer[process_p] = value;
                    process_p++;

                    while (process_p >= 3) {
                        int iv;

                        iv = (1600 * process_buffer[0]) + (40 * process_buffer[1])
                                + (process_buffer[2]) + 1;
                        target[tp] = iv / 256;
                        tp++;
                        target[tp] = iv % 256;
                        tp++;
                        binary[binary_length] = ' ';
                        binary_length++;
                        binary[binary_length] = ' ';
                        binary_length++;
                        info("(" + process_buffer[0] + " " + process_buffer[1] + " " + process_buffer[2] + ") ");

                        process_buffer[0] = process_buffer[3];
                        process_buffer[1] = process_buffer[4];
                        process_buffer[2] = process_buffer[5];
                        process_buffer[3] = 0;
                        process_buffer[4] = 0;
                        process_buffer[5] = 0;
                        process_p -= 3;
                    }
                    sp++;
                }
            }

            /* step (e) X12 encodation */
            if (current_mode == Mode.DM_X12) {
                int value = 0;

                next_mode = Mode.DM_X12;
                if (process_p == 0) {
                    next_mode = lookAheadTest(sp, current_mode);
                }

                if (next_mode != Mode.DM_X12) {
                    target[tp] = 254;
                    tp++;
                    binary[binary_length] = ' ';
                    binary_length++; /* Unlatch */

                    next_mode = Mode.DM_ASCII;
                    info("ASC ");
                } else {
                    if (inputData[sp] == 13) {
                        value = 0;
                    }
                    if (inputData[sp] == '*') {
                        value = 1;
                    }
                    if (inputData[sp] == '>') {
                        value = 2;
                    }
                    if (inputData[sp] == ' ') {
                        value = 3;
                    }
                    if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) {
                        value = (inputData[sp] - '0') + 4;
                    }
                    if ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')) {
                        value = (inputData[sp] - 'A') + 14;
                    }

                    process_buffer[process_p] = value;
                    process_p++;

                    while (process_p >= 3) {
                        int iv;

                        iv = (1600 * process_buffer[0]) + (40 * process_buffer[1])
                                + (process_buffer[2]) + 1;
                        target[tp] = iv / 256;
                        tp++;
                        target[tp] = iv % 256;
                        tp++;
                        binary[binary_length] = ' ';
                        binary_length++;
                        binary[binary_length] = ' ';
                        binary_length++;
                        info("(" + process_buffer[0] + " " + process_buffer[1] + " " + process_buffer[2] + ") ");

                        process_buffer[0] = process_buffer[3];
                        process_buffer[1] = process_buffer[4];
                        process_buffer[2] = process_buffer[5];
                        process_buffer[3] = 0;
                        process_buffer[4] = 0;
                        process_buffer[5] = 0;
                        process_p -= 3;
                    }
                    sp++;
                }
            }

            /* step (f) EDIFACT encodation */
            if (current_mode == Mode.DM_EDIFACT) {
                int value = 0;

                next_mode = Mode.DM_EDIFACT;
                if (process_p == 3) {
                    next_mode = lookAheadTest(sp, current_mode);
                }

                if (next_mode != Mode.DM_EDIFACT) {
                    process_buffer[process_p] = 31;
                    process_p++;
                    next_mode = Mode.DM_ASCII;
                } else {
                    if ((inputData[sp] >= '@') && (inputData[sp] <= '^')) {
                        value = inputData[sp] - '@';
                    }
                    if ((inputData[sp] >= ' ') && (inputData[sp] <= '?')) {
                        value = inputData[sp];
                    }

                    process_buffer[process_p] = value;
                    process_p++;
                    sp++;
                }

                while (process_p >= 4) {
                    target[tp] = (process_buffer[0] << 2)
                            + ((process_buffer[1] & 0x30) >> 4);
                    tp++;
                    target[tp] = ((process_buffer[1] & 0x0f) << 4)
                            + ((process_buffer[2] & 0x3c) >> 2);
                    tp++;
                    target[tp] = ((process_buffer[2] & 0x03) << 6)
                            + process_buffer[3];
                    tp++;
                    binary[binary_length] = ' ';
                    binary_length++;
                    binary[binary_length] = ' ';
                    binary_length++;
                    binary[binary_length] = ' ';
                    binary_length++;
                    info("(" + process_buffer[0] + " " + process_buffer[1] + " " + process_buffer[2] + ") ");

                    process_buffer[0] = process_buffer[4];
                    process_buffer[1] = process_buffer[5];
                    process_buffer[2] = process_buffer[6];
                    process_buffer[3] = process_buffer[7];
                    process_buffer[4] = 0;
                    process_buffer[5] = 0;
                    process_buffer[6] = 0;
                    process_buffer[7] = 0;
                    process_p -= 4;
                }
            }

            /* step (g) Base 256 encodation */
            if (current_mode == Mode.DM_BASE256) {
                next_mode = lookAheadTest(sp, current_mode);

                if (next_mode == Mode.DM_BASE256) {
                    target[tp] = inputData[sp];
                    infoSpace(target[tp]);
                    tp++;
                    sp++;
                    binary[binary_length] = 'b';
                    binary_length++;
                } else {
                    next_mode = Mode.DM_ASCII;
                    info("ASC ");
                }
            }

            if (tp > 1558) {
                throw new OkapiException("Data too long to fit in symbol");
            }

        } /* while */

        /* Add length and randomising algorithm to b256 */
        i = 0;
        while (i < tp) {
            if (binary[i] == 'b') {
                if ((i == 0) || (binary[i - 1] != 'b')) {
                    /* start of binary data */
                    int binary_count; /* length of b256 data */

                    for (binary_count = 0; binary_count + i < tp && binary[binary_count + i] == 'b';
                            binary_count++);

                    if (binary_count <= 249) {
                        insertAt(i, 'b');
                        insertValueAt(i, tp, (char) binary_count);
                        tp++;
                    } else {
                        insertAt(i, 'b');
                        insertAt(i + 1, 'b');
                        insertValueAt(i, tp, (char) ((binary_count / 250) + 249));
                        tp++;
                        insertValueAt(i + 1, tp, (char) (binary_count % 250));
                        tp++;
                    }
                }
            }
            i++;
        }

        for (i = 0; i < tp; i++) {
            if (binary[i] == 'b') {
                int prn, temp;

                prn = ((149 * (i + 1)) % 255) + 1;
                temp = target[i] + prn;
                if (temp <= 255) {
                    target[i] = temp;
                } else {
                    target[i] = temp - 256;
                }
            }
        }

        infoLine();
        info("Codewords: ");
        for (i = 0; i < tp; i++) {
            infoSpace(target[i]);
        }
        infoLine();

        last_mode = current_mode;
        return tp;
    }

    private int encodeRemainder(int symbols_left, int target_length) {

        int inputlen = inputData.length;

        switch (last_mode) {
            case DM_C40:
            case DM_TEXT:
                if (process_p == 1) // 1 data character left to encode.
                {
                    if (symbols_left > 1) {
                        target[target_length] = 254;
                        target_length++; // Unlatch and encode remaining data in ascii.
                    }
                    target[target_length] = inputData[inputlen - 1] + 1;
                    target_length++;
                } else if (process_p == 2) // 2 data characters left to encode.
                {
                    // Pad with shift 1 value (0) and encode as double.
                    int intValue = (1600 * process_buffer[0]) + (40 * process_buffer[1]) + 1; // ie (0 + 1).
                    target[target_length] = (intValue / 256);
                    target_length++;
                    target[target_length] = (intValue % 256);
                    target_length++;
                    if (symbols_left > 2) {
                        target[target_length] = 254; // Unlatch
                        target_length++;
                    }
                } else {
                    if (symbols_left > 0) {
                        target[target_length] = 254; // Unlatch
                        target_length++;
                    }
                }
                break;

            case DM_X12:
                if ((symbols_left == process_p) && (process_p == 1)) {
                    // Unlatch not required!
                    target[target_length] = inputData[inputlen - 1] + 1;
                    target_length++;
                } else {
                    target[target_length] = (254);
                    target_length++; // Unlatch.

                    if (process_p == 1) {
                        target[target_length] = inputData[inputlen - 1] + 1;
                        target_length++;
                    }

                    if (process_p == 2) {
                        target[target_length] = inputData[inputlen - 2] + 1;
                        target_length++;
                        target[target_length] = inputData[inputlen - 1] + 1;
                        target_length++;
                    }
                }
                break;

            case DM_EDIFACT:
                if (symbols_left <= 2) // Unlatch not required!
                {
                    if (process_p == 1) {
                        target[target_length] = inputData[inputlen - 1] + 1;
                        target_length++;
                    }

                    if (process_p == 2) {
                        target[target_length] = inputData[inputlen - 2] + 1;
                        target_length++;
                        target[target_length] = inputData[inputlen - 1] + 1;
                        target_length++;
                    }
                } else {
                    // Append edifact unlatch value (31) and empty buffer
                    if (process_p == 0) {
                        target[target_length] = (31 << 2);
                        target_length++;
                    }

                    if (process_p == 1) {
                        target[target_length] = ((process_buffer[0] << 2) + ((31 & 0x30) >> 4));
                        target_length++;
                        target[target_length] = ((31 & 0x0f) << 4);
                        target_length++;
                    }

                    if (process_p == 2) {
                        target[target_length] = ((process_buffer[0] << 2) + ((process_buffer[1] & 0x30) >> 4));
                        target_length++;
                        target[target_length] = (((process_buffer[1] & 0x0f) << 4) + ((31 & 0x3c) >> 2));
                        target_length++;
                        target[target_length] = (((31 & 0x03) << 6));
                        target_length++;
                    }

                    if (process_p == 3) {
                        target[target_length] = ((process_buffer[0] << 2) + ((process_buffer[1] & 0x30) >> 4));
                        target_length++;
                        target[target_length] = (((process_buffer[1] & 0x0f) << 4) + ((process_buffer[2] & 0x3c) >> 2));
                        target_length++;
                        target[target_length] = (((process_buffer[2] & 0x03) << 6) + 31);
                        target_length++;
                    }
                }
                break;
        }

        return target_length;
    }

    private boolean isTwoDigits(int pos) {
        return pos + 1 < inputData.length &&
               Character.isDigit((char) inputData[pos]) &&
               Character.isDigit((char) inputData[pos + 1]);
    }

    private Mode lookAheadTest(int position, Mode current_mode) {

        /* 'look ahead test' from Annex P */

        double ascii_count, c40_count, text_count, x12_count, edf_count, b256_count, best_count;
        int sp;
        int sourcelen = inputData.length;
        Mode best_scheme = Mode.NULL;
        double stiction = (1.0F / 24.0F); // smallest change to act on, to get around floating point inaccuracies

        /* step (j) */
        if (current_mode == Mode.DM_ASCII) {
            ascii_count = 0.0;
            c40_count = 1.0;
            text_count = 1.0;
            x12_count = 1.0;
            edf_count = 1.0;
            b256_count = 1.25;
        } else {
            ascii_count = 1.0;
            c40_count = 2.0;
            text_count = 2.0;
            x12_count = 2.0;
            edf_count = 2.0;
            b256_count = 2.25;
        }

        switch (current_mode) {
            case DM_C40: // (j)(2)
                c40_count = 0.0;
                break;
            case DM_TEXT: // (j)(3)
                text_count = 0.0;
                break;
            case DM_X12: // (j)(4)
                x12_count = 0.0;
                break;
            case DM_EDIFACT: // (j)(5)
                edf_count = 0.0;
                break;
            case DM_BASE256: // (j)(6)
                b256_count = 0.0;
                break;
        }

        sp = position;

        do {
            if (sp == sourcelen) {
                /* At the end of data ... step (k) */
                ascii_count = Math.ceil(ascii_count);
                b256_count = Math.ceil(b256_count);
                edf_count = Math.ceil(edf_count);
                text_count = Math.ceil(text_count);
                x12_count = Math.ceil(x12_count);
                c40_count = Math.ceil(c40_count);

                best_count = c40_count;
                best_scheme = Mode.DM_C40; // (k)(7)

                if (x12_count < (best_count - stiction)) {
                    best_count = x12_count;
                    best_scheme = Mode.DM_X12; // (k)(6)
                }

                if (text_count < (best_count - stiction)) {
                    best_count = text_count;
                    best_scheme = Mode.DM_TEXT; // (k)(5)
                }

                if (edf_count < (best_count - stiction)) {
                    best_count = edf_count;
                    best_scheme = Mode.DM_EDIFACT; // (k)(4)
                }

                if (b256_count < (best_count - stiction)) {
                    best_count = b256_count;
                    best_scheme = Mode.DM_BASE256; // (k)(3)
                }

                if (ascii_count <= (best_count + stiction)) {
                    best_scheme = Mode.DM_ASCII; // (k)(2)
                }
            } else {

                /* ascii ... step (l) */
                if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) {
                    ascii_count += 0.5; // (l)(1)
                } else {
                    if (inputData[sp] > 127) {
                        ascii_count = Math.ceil(ascii_count) + 2.0; // (l)(2)
                    } else {
                        ascii_count = Math.ceil(ascii_count) + 1.0; // (l)(3)
                    }
                }

                /* c40 ... step (m) */
                if ((inputData[sp] == ' ') ||
                       (((inputData[sp] >= '0') && (inputData[sp] <= '9')) ||
                       ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')))) {
                    c40_count += (2.0 / 3.0); // (m)(1)
                } else {
                    if (inputData[sp] > 127) {
                        c40_count += (8.0 / 3.0); // (m)(2)
                    } else {
                        c40_count += (4.0 / 3.0); // (m)(3)
                    }
                }

                /* text ... step (n) */
                if ((inputData[sp] == ' ') ||
                       (((inputData[sp] >= '0') && (inputData[sp] <= '9')) ||
                       ((inputData[sp] >= 'a') && (inputData[sp] <= 'z')))) {
                    text_count += (2.0 / 3.0); // (n)(1)
                } else {
                    if (inputData[sp] > 127) {
                        text_count += (8.0 / 3.0); // (n)(2)
                    } else {
                        text_count += (4.0 / 3.0); // (n)(3)
                    }
                }

                /* x12 ... step (o) */
                if (isX12(inputData[sp])) {
                    x12_count += (2.0 / 3.0); // (o)(1)
                } else {
                    if (inputData[sp] > 127) {
                        x12_count += (13.0 / 3.0); // (o)(2)
                    } else {
                        x12_count += (10.0 / 3.0); // (o)(3)
                    }
                }

                /* edifact ... step (p) */
                if ((inputData[sp] >= ' ') && (inputData[sp] <= '^')) {
                    edf_count += (3.0 / 4.0); // (p)(1)
                } else {
                    if (inputData[sp] > 127) {
                        edf_count += 17.0; // (p)(2) > Value changed from ISO
                    } else {
                        edf_count += 13.0; // (p)(3) > Value changed from ISO
                    }
                }
                if (inputData[sp] == FNC1) {
                    edf_count += 13.0; //  > Value changed from ISO
                }

                /* base 256 ... step (q) */
                if (inputData[sp] == FNC1) {
                    b256_count += 4.0; // (q)(1)
                } else {
                    b256_count += 1.0; // (q)(2)
                }
            }


            if (sp > (position + 3)) {
                /* 4 data characters processed ... step (r) */

                /* step (r)(6) */
                if (((c40_count + 1.0) < (ascii_count - stiction)) &&
                        ((c40_count + 1.0) < (b256_count - stiction)) &&
                        ((c40_count + 1.0) < (edf_count - stiction)) &&
                        ((c40_count + 1.0) < (text_count - stiction))) {

                    if (c40_count < (x12_count - stiction)) {
                        best_scheme = Mode.DM_C40;
                    }

                    if ((c40_count >= (x12_count - stiction))
                            && (c40_count <= (x12_count + stiction))) {
                        if (p_r_6_2_1(sp, sourcelen)) {
                            // Test (r)(6)(ii)(i)
                            best_scheme = Mode.DM_X12;
                        } else {
                            best_scheme = Mode.DM_C40;
                        }
                    }
                }

                /* step (r)(5) */
                if (((x12_count + 1.0) < (ascii_count - stiction)) &&
                        ((x12_count + 1.0) < (b256_count - stiction)) &&
                        ((x12_count + 1.0) < (edf_count - stiction)) &&
                        ((x12_count + 1.0) < (text_count - stiction)) &&
                        ((x12_count + 1.0) < (c40_count - stiction))) {
                    best_scheme = Mode.DM_X12;
                }

                /* step (r)(4) */
                if (((text_count + 1.0) < (ascii_count - stiction)) &&
                        ((text_count + 1.0) < (b256_count - stiction)) &&
                        ((text_count + 1.0) < (edf_count - stiction)) &&
                        ((text_count + 1.0) < (x12_count - stiction)) &&
                        ((text_count + 1.0) < (c40_count - stiction))) {
                    best_scheme = Mode.DM_TEXT;
                }

                /* step (r)(3) */
                if (((edf_count + 1.0) < (ascii_count - stiction)) &&
                        ((edf_count + 1.0) < (b256_count - stiction)) &&
                        ((edf_count + 1.0) < (text_count - stiction)) &&
                        ((edf_count + 1.0) < (x12_count - stiction)) &&
                        ((edf_count + 1.0) < (c40_count - stiction))) {
                    best_scheme = Mode.DM_EDIFACT;
                }

                /* step (r)(2) */
                if (((b256_count + 1.0) <= (ascii_count + stiction)) ||
                        (((b256_count + 1.0) < (edf_count - stiction)) &&
                        ((b256_count + 1.0) < (text_count - stiction)) &&
                        ((b256_count + 1.0) < (x12_count - stiction)) &&
                        ((b256_count + 1.0) < (c40_count - stiction)))) {
                    best_scheme = Mode.DM_BASE256;
                }

                /* step (r)(1) */
                if (((ascii_count + 1.0) <= (b256_count + stiction)) &&
                        ((ascii_count + 1.0) <= (edf_count + stiction)) &&
                        ((ascii_count + 1.0) <= (text_count + stiction)) &&
                        ((ascii_count + 1.0) <= (x12_count + stiction)) &&
                        ((ascii_count + 1.0) <= (c40_count + stiction))) {
                    best_scheme = Mode.DM_ASCII;
                }
            }

            sp++;

        } while (best_scheme == Mode.NULL); // step (s)

        return best_scheme;
    }

    private boolean p_r_6_2_1(int position, int sourcelen) {
        /* Annex P section (r)(6)(ii)(I)
           "If one of the three X12 terminator/separator characters first
            occurs in the yet to be processed data before a non-X12 character..."
        */

        int i;
        int nonX12Position = 0;
        int specialX12Position = 0;
        boolean retval = false;

        for (i = position; i < sourcelen; i++) {
            if (nonX12Position == 0 && !isX12(inputData[i])) {
                nonX12Position = i;
            }

            if (specialX12Position == 0) {
                if ((inputData[i] == (char) 13) ||
                    (inputData[i] == '*') ||
                    (inputData[i] == '>')) {
                    specialX12Position = i;
                }
            }
        }

        if ((nonX12Position != 0) && (specialX12Position != 0)) {
            if (specialX12Position < nonX12Position) {
                retval = true;
            }
        }

        return retval;
    }

    private boolean isX12(int source) {
        if (source == 13) {
            return true;
        }
        if (source == 42) {
            return true;
        }
        if (source == 62) {
            return true;
        }
        if (source == 32) {
            return true;
        }
        if ((source >= '0') && (source <= '9')) {
            return true;
        }
        if ((source >= 'A') && (source <= 'Z')) {
            return true;
        }

        return false;
    }

    private void calculateErrorCorrection(int bytes, int datablock, int rsblock, int skew) {
        // calculate and append ecc code, and if necessary interleave
        int blocks = (bytes + 2) / datablock, b;
        int n, p;
        ReedSolomon rs = new ReedSolomon();

        rs.init_gf(0x12d);
        rs.init_code(rsblock, 1);

        for (b = 0; b < blocks; b++) {
            int[] buf = new int[256];
            int[] ecc = new int[256];

            p = 0;
            for (n = b; n < bytes; n += blocks) {
                buf[p++] = target[n];
            }
            rs.encode(p, buf);
            for (n = 0; n < rsblock; n++) {
                ecc[n] = rs.getResult(n);
            }
            p = rsblock - 1; // comes back reversed
            for (n = b; n < rsblock * blocks; n += blocks) {
                if (skew == 1) {
                    /* Rotate ecc data to make 144x144 size symbols acceptable */
                    /* See http://groups.google.com/group/postscriptbarcode/msg/5ae8fda7757477da */
                    if (b < 8) {
                        target[bytes + n + 2] = ecc[p--];
                    } else {
                        target[bytes + n - 8] = ecc[p--];
                    }
                } else {
                    target[bytes + n] = ecc[p--];
                }
            }
        }
    }

    private void insertAt(int pos, char newbit) {
        /* Insert a character into the middle of a string at position posn */
        for (int i = binary_length; i > pos; i--) {
            binary[i] = binary[i - 1];
        }
        binary[pos] = newbit;
        binary_length++;
    }

    private void insertValueAt(int posn, int streamlen, char newbit) {
        int i;

        for (i = streamlen; i > posn; i--) {
            target[i] = target[i - 1];
        }
        target[posn] = newbit;
    }

    private void addPadBits(int tp, int tail_length) {
        int i, prn, temp;

        for (i = tail_length; i > 0; i--) {
            if (i == tail_length) {
                target[tp] = 129;
                tp++; /* Pad */
            } else {
                prn = ((149 * (tp + 1)) % 253) + 1;
                temp = 129 + prn;
                if (temp <= 254) {
                    target[tp] = temp;
                    tp++;
                } else {
                    target[tp] = temp - 254;
                    tp++;
                }
            }
        }
    }

    private void placeData(int NR, int NC) {
        int r, c, p;
        // invalidate
        for (r = 0; r < NR; r++) {
            for (c = 0; c < NC; c++) {
                places[r * NC + c] = 0;
            }
        }
        // start
        p = 1;
        r = 4;
        c = 0;
        do {
            // check corner
            if (r == NR && (c == 0)) {
                placeCornerA(NR, NC, p++);
            }
            if (r == NR - 2 && (c == 0) && ((NC % 4) != 0)) {
                placeCornerB(NR, NC, p++);
            }
            if (r == NR - 2 && (c == 0) && (NC % 8) == 4) {
                placeCornerC(NR, NC, p++);
            }
            if (r == NR + 4 && c == 2 && ((NC % 8) == 0)) {
                placeCornerD(NR, NC, p++);
            }
            // up/right
            do {
                if (r < NR && c >= 0 && (places[r * NC + c] == 0)) {
                    placeBlock(NR, NC, r, c, p++);
                }
                r -= 2;
                c += 2;
            } while (r >= 0 && c < NC);
            r++;
            c += 3;
            // down/left
            do {
                if (r >= 0 && c < NC && (places[r * NC + c] == 0)) {
                    placeBlock(NR, NC, r, c, p++);
                }
                r += 2;
                c -= 2;
            } while (r < NR && c >= 0);
            r += 3;
            c++;
        } while (r < NR || c < NC);
        // unfilled corner
        if (places[NR * NC - 1] == 0) {
            places[NR * NC - 1] = places[NR * NC - NC - 2] = 1;
        }
    }

    private void placeCornerA(int NR, int NC, int p) {
        placeBit(NR, NC, NR - 1, 0, p, 7);
        placeBit(NR, NC, NR - 1, 1, p, 6);
        placeBit(NR, NC, NR - 1, 2, p, 5);
        placeBit(NR, NC, 0, NC - 2, p, 4);
        placeBit(NR, NC, 0, NC - 1, p, 3);
        placeBit(NR, NC, 1, NC - 1, p, 2);
        placeBit(NR, NC, 2, NC - 1, p, 1);
        placeBit(NR, NC, 3, NC - 1, p, 0);
    }

    private void placeCornerB(int NR, int NC, int p) {
        placeBit(NR, NC, NR - 3, 0, p, 7);
        placeBit(NR, NC, NR - 2, 0, p, 6);
        placeBit(NR, NC, NR - 1, 0, p, 5);
        placeBit(NR, NC, 0, NC - 4, p, 4);
        placeBit(NR, NC, 0, NC - 3, p, 3);
        placeBit(NR, NC, 0, NC - 2, p, 2);
        placeBit(NR, NC, 0, NC - 1, p, 1);
        placeBit(NR, NC, 1, NC - 1, p, 0);
    }

    private void placeCornerC(int NR, int NC, int p) {
        placeBit(NR, NC, NR - 3, 0, p, 7);
        placeBit(NR, NC, NR - 2, 0, p, 6);
        placeBit(NR, NC, NR - 1, 0, p, 5);
        placeBit(NR, NC, 0, NC - 2, p, 4);
        placeBit(NR, NC, 0, NC - 1, p, 3);
        placeBit(NR, NC, 1, NC - 1, p, 2);
        placeBit(NR, NC, 2, NC - 1, p, 1);
        placeBit(NR, NC, 3, NC - 1, p, 0);
    }

    private void placeCornerD(int NR, int NC, int p) {
        placeBit(NR, NC, NR - 1, 0, p, 7);
        placeBit(NR, NC, NR - 1, NC - 1, p, 6);
        placeBit(NR, NC, 0, NC - 3, p, 5);
        placeBit(NR, NC, 0, NC - 2, p, 4);
        placeBit(NR, NC, 0, NC - 1, p, 3);
        placeBit(NR, NC, 1, NC - 3, p, 2);
        placeBit(NR, NC, 1, NC - 2, p, 1);
        placeBit(NR, NC, 1, NC - 1, p, 0);
    }

    private void placeBlock(int NR, int NC, int r, int c, int p) {
        placeBit(NR, NC, r - 2, c - 2, p, 7);
        placeBit(NR, NC, r - 2, c - 1, p, 6);
        placeBit(NR, NC, r - 1, c - 2, p, 5);
        placeBit(NR, NC, r - 1, c - 1, p, 4);
        placeBit(NR, NC, r - 1, c - 0, p, 3);
        placeBit(NR, NC, r - 0, c - 2, p, 2);
        placeBit(NR, NC, r - 0, c - 1, p, 1);
        placeBit(NR, NC, r - 0, c - 0, p, 0);
    }

    private void placeBit(int NR, int NC, int r, int c, int p, int b) {
        if (r < 0) {
            r += NR;
            c += 4 - ((NR + 4) % 8);
        }
        if (c < 0) {
            c += NC;
            r += 4 - ((NC + 4) % 8);
        }
        places[r * NC + c] = (p << 3) + b;
    }
}
 public enum ForceMode {
    NONE, SQUARE, RECTANGULAR
}
 public class Ean extends Symbol {

    public enum Mode {
        EAN8, EAN13
    }

    private static final String[] EAN13_PARITY = {
        "AAAAAA", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA",
        "ABABAB", "ABABBA", "ABBABA"
    };

    private static final String[] EAN_SET_A = {
        "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", "3112"
    };

    private static final String[] EAN_SET_B = {
        "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", "2113"
    };

    private Mode mode = Mode.EAN13;
    private int guardPatternExtraHeight = 5;
    private boolean linkageFlag;
    private EanUpcAddOn addOn;

    /** Creates a new instance. */
    public Ean() {
        this.humanReadableAlignment = HumanReadableAlignment.JUSTIFY;
    }

    /**
     * Sets the EAN mode (EAN-8 or EAN-13). The default is EAN-13.
     *
     * @param mode the EAN mode (EAN-8 or EAN-13)
     */
    public void setMode(Mode mode) {
        this.mode = mode;
    }

    /**
     * Returns the EAN mode (EAN-8 or EAN-13).
     *
     * @return the EAN mode (EAN-8 or EAN-13)
     */
    public Mode getMode() {
        return mode;
    }

    /**
     * Sets the extra height used for the guard patterns. The default value is <code>5</code>.
     *
     * @param guardPatternExtraHeight the extra height used for the guard patterns
     */
    public void setGuardPatternExtraHeight(int guardPatternExtraHeight) {
        this.guardPatternExtraHeight = guardPatternExtraHeight;
    }

    /**
     * Returns the extra height used for the guard patterns.
     *
     * @return the extra height used for the guard patterns
     */
    public int getGuardPatternExtraHeight() {
        return guardPatternExtraHeight;
    }

    /**
     * Sets the linkage flag. If set to <code>true</code>, this symbol is part of a composite symbol.
     *
     * @param linkageFlag the linkage flag
     */
    protected void setLinkageFlag(boolean linkageFlag) {
        this.linkageFlag = linkageFlag;
    }

    @Override
    protected void encode() {

        separateContent();

        if (content.isEmpty()) {
            throw new OkapiException("Missing EAN data");
        }

        if (mode == Mode.EAN8) {
            ean8();
        } else {
            ean13();
        }
    }

    private void separateContent() {
        int splitPoint = content.indexOf('+');
        if (splitPoint == -1) {
            // there is no add-on data
            addOn = null;
        } else if (splitPoint == content.length() - 1) {
            // we found the add-on separator, but no add-on data
            throw new OkapiException("Invalid add-on data");
        } else {
            // there is a '+' in the input data, use an add-on EAN2 or EAN5
            addOn = new EanUpcAddOn();
            addOn.font = this.font;
            addOn.fontName = this.fontName;
            addOn.fontSize = this.fontSize;
            addOn.humanReadableLocation = (this.humanReadableLocation == NONE ? NONE : TOP);
            addOn.moduleWidth = this.moduleWidth;
            addOn.default_height = this.default_height + this.guardPatternExtraHeight - 8;
            addOn.setContent(content.substring(splitPoint + 1));
            content = content.substring(0, splitPoint);
        }
    }

    private void ean13() {

        content = validateAndPad(content, 12);

        char check = calcDigit(content);
        infoLine("Check Digit: " + check);

        String hrt = content + check;
        char parityChar = hrt.charAt(0);
        String parity = EAN13_PARITY[parityChar - '0'];
        infoLine("Parity Digit: " + parityChar);

        StringBuilder dest = new StringBuilder("111");
        for (int i = 1; i < 13; i++) {
            if (i == 7) {
                dest.append("11111");
            }
            if (i <= 6) {
                if (parity.charAt(i - 1) == 'B') {
                    dest.append(EAN_SET_B[hrt.charAt(i) - '0']);
                } else {
                    dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
                }
            } else {
                dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
            }
        }
        dest.append("111");

        readable = hrt;
        pattern = new String[] { dest.toString() };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void ean8() {

        content = validateAndPad(content, 7);

        char check = calcDigit(content);
        infoLine("Check Digit: " + check);

        String hrt = content + check;

        StringBuilder dest = new StringBuilder("111");
        for (int i = 0; i < 8; i++) {
            if (i == 4) {
                dest.append("11111");
            }
            dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
        }
        dest.append("111");

        readable = hrt;
        pattern = new String[] { dest.toString() };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    protected static String validateAndPad(String s, int targetLength) {

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

        if (s.length() > targetLength) {
            throw new OkapiException("Input data too long");
        }

        if (s.length() < targetLength) {
            for (int i = s.length(); i < targetLength; i++) {
                s = '0' + s;
            }
        }

        return s;
    }

    protected static char calcDigit(String s) {

        int count = 0;
        int p = 0;

        for (int i = s.length() - 1; i >= 0; i--) {
            int c = Character.getNumericValue(s.charAt(i));
            if (p % 2 == 0) {
                c = c * 3;
            }
            count += c;
            p++;
        }

        int cdigit = 10 - (count % 10);
        if (cdigit == 10) {
            cdigit = 0;
        }

        return (char) (cdigit + '0');
    }

    @Override
    protected void plotSymbol() {

        int xBlock;
        int x, y, w, h;
        boolean black = true;
        int compositeOffset = (linkageFlag ? 6 : 0); // space for composite separator above
        int hrtOffset = (humanReadableLocation == TOP ? getTheoreticalHumanReadableHeight() : 0); // space for HRT above

        rectangles.clear();
        texts.clear();
        x = 0;

        /* Draw the bars in the symbology */
        for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {

            w = pattern[0].charAt(xBlock) - '0';

            if (black) {
                y = 0;
                h = default_height;
                /* Add extension to guide bars */
                if (mode == Mode.EAN13) {
                    if (x < 3 || x > 91 || (x > 45 && x < 49)) {
                        h += guardPatternExtraHeight;
                    }
                    if (linkageFlag && (x == 0 || x == 94)) {
                        h += 2;
                        y -= 2;
                    }
                } else {
                    if (x < 3 || x > 62 || (x > 30 && x < 35)) {
                        h += guardPatternExtraHeight;
                    }
                    if (linkageFlag && (x == 0 || x == 66)) {
                        h += 2;
                        y -= 2;
                    }
                }
                Rectangle2D.Double rect = new Rectangle2D.Double(scale(x), y + compositeOffset + hrtOffset, scale(w), h);
                rectangles.add(rect);
                symbol_width = Math.max(symbol_width, (int) rect.getMaxX());
                symbol_height = Math.max(symbol_height, (int) rect.getHeight());
            }

            black = !black;
            x += w;
        }

        /* Add separator for composite symbology, if necessary */
        if (linkageFlag) {
            if (mode == Mode.EAN13) {
                rectangles.add(new Rectangle2D.Double(scale(0),  0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(94), 0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(95), 2, scale(1), 2));
            } else { // EAN8
                rectangles.add(new Rectangle2D.Double(scale(0),  0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(66), 0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(67), 2, scale(1), 2));
            }
            symbol_height += 4;
        }

        /* Now add the text */
        if (humanReadableLocation == BOTTOM) {
            symbol_height -= guardPatternExtraHeight;
            double baseline = symbol_height + fontSize;
            if (mode == Mode.EAN13) {
                texts.add(new TextBox(scale(-9), baseline, scale(4),  readable.substring(0, 1), HumanReadableAlignment.RIGHT));
                texts.add(new TextBox(scale(5),  baseline, scale(39), readable.substring(1, 7), humanReadableAlignment));
                texts.add(new TextBox(scale(51), baseline, scale(39), readable.substring(7, 13), humanReadableAlignment));
            } else { // EAN8
                texts.add(new TextBox(scale(5),  baseline, scale(25), readable.substring(0, 4), humanReadableAlignment));
                texts.add(new TextBox(scale(37), baseline, scale(25), readable.substring(4, 8), humanReadableAlignment));
            }
        } else if (humanReadableLocation == TOP) {
            double baseline = fontSize;
            int width = (mode == Mode.EAN13 ? 94 : 66);
            texts.add(new TextBox(scale(0), baseline, scale(width), readable, humanReadableAlignment));
        }

        /* Now add the add-on symbol, if necessary */
        if (addOn != null) {
            int gap = 9;
            int baseX = symbol_width + scale(gap);
            Rectangle2D.Double r1 = rectangles.get(0);
            Rectangle2D.Double ar1 = addOn.rectangles.get(0);
            int baseY = (int) (r1.y + r1.getHeight() - ar1.y - ar1.getHeight());
            for (TextBox t : addOn.getTexts()) {
                texts.add(new TextBox(baseX + t.x, baseY + t.y, t.width, t.text, t.alignment));
            }
            for (Rectangle2D.Double r : addOn.getRectangles()) {
                rectangles.add(new Rectangle2D.Double(baseX + r.x, baseY + r.y, r.width, r.height));
            }
            symbol_width += scale(gap) + addOn.symbol_width;
            pattern[0] = pattern[0] + gap + addOn.pattern[0];
        }
    }

    /** Scales the specified width or x-dimension according to the current module width. */
    private int scale(int w) {
        return moduleWidth * w;
    }
} public class GridMatrix extends Symbol {

    private static final char[] SHIFT_SET = {
        /* From Table 7 - Encoding of control characters */
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /* NULL -> SI */
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* DLE -> US */
        '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':',
        ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'
    };

    private static final int[] GM_RECOMMEND_CW = {
        9, 30, 59, 114, 170, 237, 315, 405, 506, 618, 741, 875, 1021
    };

    private static final int[] GM_MAX_CW = {
        11, 40, 79, 146, 218, 305, 405, 521, 650, 794, 953, 1125, 1313
    };

    private static final int[] GM_DATA_CODEWORDS = {
        0, 15, 13, 11, 9,
        45, 40, 35, 30, 25,
        89, 79, 69, 59, 49,
        146, 130, 114, 98, 81,
        218, 194, 170, 146, 121,
        305, 271, 237, 203, 169,
        405, 360, 315, 270, 225,
        521, 463, 405, 347, 289,
        650, 578, 506, 434, 361,
        794, 706, 618, 530, 441,
        953, 847, 741, 635, 529,
        1125, 1000, 875, 750, 625,
        1313, 1167, 1021, 875, 729
    };

    private static final int[] GM_N1 = {
        18, 50, 98, 81, 121, 113, 113, 116, 121, 126, 118, 125, 122
    };
    private static final int[] GM_B1 = {
        1, 1, 1, 2, 2, 2, 2, 3, 2, 7, 5, 10, 6
    };
    private static final int[] GM_B2 = {
        0, 0, 0, 0, 0, 1, 2, 2, 4, 0, 4, 0, 6
    };

    private static final int[] GM_EBEB = {
        /* E1 B3 E2 B4 */
        0, 0, 0, 0, // version 1
        3, 1, 0, 0,
        5, 1, 0, 0,
        7, 1, 0, 0,
        9, 1, 0, 0,
        5, 1, 0, 0, // version 2
        10, 1, 0, 0,
        15, 1, 0, 0,
        20, 1, 0, 0,
        25, 1, 0, 0,
        9, 1, 0, 0, // version 3
        19, 1, 0, 0,
        29, 1, 0, 0,
        39, 1, 0, 0,
        49, 1, 0, 0,
        8, 2, 0, 0, // version 4
        16, 2, 0, 0,
        24, 2, 0, 0,
        32, 2, 0, 0,
        41, 1, 10, 1,
        12, 2, 0, 0, // version 5
        24, 2, 0, 0,
        36, 2, 0, 0,
        48, 2, 0, 0,
        61, 1, 60, 1,
        11, 3, 0, 0, // version 6
        23, 1, 22, 2,
        34, 2, 33, 1,
        45, 3, 0, 0,
        57, 1, 56, 2,
        12, 1, 11, 3, // version 7
        23, 2, 22, 2,
        34, 3, 33, 1,
        45, 4, 0, 0,
        57, 1, 56, 3,
        12, 2, 11, 3, // version 8
        23, 5, 0, 0,
        35, 3, 34, 2,
        47, 1, 46, 4,
        58, 4, 57, 1,
        12, 6, 0, 0, // version 9
        24, 6, 0, 0,
        36, 6, 0, 0,
        48, 6, 0, 0,
        61, 1, 60, 5,
        13, 4, 12, 3, // version 10
        26, 1, 25, 6,
        38, 5, 37, 2,
        51, 2, 50, 5,
        63, 7, 0, 0,
        12, 6, 11, 3, // version 11
        24, 4, 23, 5,
        36, 2, 35, 7,
        47, 9, 0, 0,
        59, 7, 58, 2,
        13, 5, 12, 5, // version 12
        25, 10, 0, 0,
        38, 5, 37, 5,
        50, 10, 0, 0,
        63, 5, 62, 5,
        13, 1, 12, 11, //version 13
        25, 3, 24, 9,
        37, 5, 36, 7,
        49, 7, 48, 5,
        61, 9, 60, 3
    };

    private static final int[] GM_MACRO_MATRIX = {
        728, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650,
        727, 624, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 651,
        726, 623, 528, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 553, 652,
        725, 622, 527, 440, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 463, 554, 653,
        724, 621, 526, 439, 360, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 381, 464, 555, 654,
        723, 620, 525, 438, 359, 288, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 307, 382, 465, 556, 655,
        722, 619, 524, 437, 358, 287, 224, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 241, 308, 383, 466, 557, 656,
        721, 618, 523, 436, 357, 286, 223, 168, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 183, 242, 309, 384, 467, 558, 657,
        720, 617, 522, 435, 356, 285, 222, 167, 120, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 133, 184, 243, 310, 385, 468, 559, 658,
        719, 616, 521, 434, 355, 284, 221, 166, 119, 80, 49, 50, 51, 52, 53, 54, 55, 56, 91, 134, 185, 244, 311, 386, 469, 560, 659,
        718, 615, 520, 433, 354, 283, 220, 165, 118, 79, 48, 25, 26, 27, 28, 29, 30, 57, 92, 135, 186, 245, 312, 387, 470, 561, 660,
        717, 614, 519, 432, 353, 282, 219, 164, 117, 78, 47, 24, 9, 10, 11, 12, 31, 58, 93, 136, 187, 246, 313, 388, 471, 562, 661,
        716, 613, 518, 431, 352, 281, 218, 163, 116, 77, 46, 23, 8, 1, 2, 13, 32, 59, 94, 137, 188, 247, 314, 389, 472, 563, 662,
        715, 612, 517, 430, 351, 280, 217, 162, 115, 76, 45, 22, 7, 0, 3, 14, 33, 60, 95, 138, 189, 248, 315, 390, 473, 564, 663,
        714, 611, 516, 429, 350, 279, 216, 161, 114, 75, 44, 21, 6, 5, 4, 15, 34, 61, 96, 139, 190, 249, 316, 391, 474, 565, 664,
        713, 610, 515, 428, 349, 278, 215, 160, 113, 74, 43, 20, 19, 18, 17, 16, 35, 62, 97, 140, 191, 250, 317, 392, 475, 566, 665,
        712, 609, 514, 427, 348, 277, 214, 159, 112, 73, 42, 41, 40, 39, 38, 37, 36, 63, 98, 141, 192, 251, 318, 393, 476, 567, 666,
        711, 608, 513, 426, 347, 276, 213, 158, 111, 72, 71, 70, 69, 68, 67, 66, 65, 64, 99, 142, 193, 252, 319, 394, 477, 568, 667,
        710, 607, 512, 425, 346, 275, 212, 157, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 143, 194, 253, 320, 395, 478, 569, 668,
        709, 606, 511, 424, 345, 274, 211, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144, 195, 254, 321, 396, 479, 570, 669,
        708, 605, 510, 423, 344, 273, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 255, 322, 397, 480, 571, 670,
        707, 604, 509, 422, 343, 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 323, 398, 481, 572, 671,
        706, 603, 508, 421, 342, 341, 340, 339, 338, 337, 336, 335, 334, 333, 332, 331, 330, 329, 328, 327, 326, 325, 324, 399, 482, 573, 672,
        705, 602, 507, 420, 419, 418, 417, 416, 415, 414, 413, 412, 411, 410, 409, 408, 407, 406, 405, 404, 403, 402, 401, 400, 483, 574, 673,
        704, 601, 506, 505, 504, 503, 502, 501, 500, 499, 498, 497, 496, 495, 494, 493, 492, 491, 490, 489, 488, 487, 486, 485, 484, 575, 674,
        703, 600, 599, 598, 597, 596, 595, 594, 593, 592, 591, 590, 589, 588, 587, 586, 585, 584, 583, 582, 581, 580, 579, 578, 577, 576, 675,
        702, 701, 700, 699, 698, 697, 696, 695, 694, 693, 692, 691, 690, 689, 688, 687, 686, 685, 684, 683, 682, 681, 680, 679, 678, 677, 676
    };

    private static final char[] MIXED_ALPHANUM_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 enum Mode {
        NULL, GM_NUMBER, GM_LOWER, GM_UPPER, GM_MIXED, GM_CONTROL, GM_BYTE, GM_CHINESE
    }

    private StringBuilder binary;
    private int[] word = new int[1460];
    private boolean[] grid;
    private Mode appxDnextSection = Mode.NULL;
    private Mode appxDlastSection = Mode.NULL;
    private int preferredVersion = 0;
    private int preferredEccLevel = -1;

    /**
     * Set preferred size, or "version" of the symbol according to the following
     * table. This value may be ignored if the data to be encoded does not fit
     * into a symbol of the selected size.
     *
     * <table>
     * <tbody>
     * <tr><th>Input</th><th>Size     </th></tr>
     * <tr><td>1    </td><td>18 x 18  </td></tr>
     * <tr><td>2    </td><td>30 x 30  </td></tr>
     * <tr><td>3    </td><td>42 x 42  </td></tr>
     * <tr><td>4    </td><td>54 x 54  </td></tr>
     * <tr><td>5    </td><td>66 x 66  </td></tr>
     * <tr><td>6    </td><td>78 x 78  </td></tr>
     * <tr><td>7    </td><td>90 x 90  </td></tr>
     * <tr><td>8    </td><td>102 x 102</td></tr>
     * <tr><td>9    </td><td>114 x 114</td></tr>
     * <tr><td>10   </td><td>126 x 126</td></tr>
     * <tr><td>11   </td><td>138 x 138</td></tr>
     * <tr><td>12   </td><td>150 x 150</td></tr>
     * <tr><td>13   </td><td>162 x 162</td></tr>
     * </tbody>
     * </table>
     *
     * @param version symbol version
     */
    public void setPreferredVersion(int version) {
        preferredVersion = version;
    }

    /**
     * Set the preferred amount of the symbol which should be dedicated to error
     * correction data. Values should be selected from the following table:
     *
     * <table>
     * <tbody>
     * <tr><th>Mode</th><th>Error Correction Capacity</th></tr>
     * <tr><td>1   </td><td>Approximately 10%        </td></tr>
     * <tr><td>2   </td><td>Approximately 20%        </td></tr>
     * <tr><td>3   </td><td>Approximately 30%        </td></tr>
     * <tr><td>4   </td><td>Approximately 40%        </td></tr>
     * <tr><td>5   </td><td>Approximately 50%        </td></tr>
     * </tbody>
     * </table>
     *
     * @param eccLevel error correction level
     */
    public void setPreferredEccLevel(int eccLevel) {
        preferredEccLevel = eccLevel;
    }

    @Override
    protected void encode() {
        int size, modules, dark, error_number;
        int auto_layers, min_layers, layers, auto_ecc_level, min_ecc_level, ecc_level;
        int x, y, i;
        int data_cw, input_latch = 0;
        int data_max;
        int length;
        StringBuilder bin = new StringBuilder();

        for (i = 0; i < 1460; i++) {
            word[i] = 0;
        }

        try {
            Charset gb2312 = Charset.forName("GB2312");
            if (gb2312.newEncoder().canEncode(content)) {
                /* GB2312 will work, use Chinese compaction */
                byte[] inputBytes = content.getBytes(gb2312);
                inputData = new int[inputBytes.length];
                length = 0;
                for (i = 0; i < inputBytes.length; i++) {
                    if (((inputBytes[i] & 0xFF) >= 0xA1) && ((inputBytes[i] & 0xFF) <= 0xF7)) {
                        /* Double byte character */
                        inputData[length] = ((inputBytes[i] & 0xFF) * 256) + (inputBytes[i + 1] & 0xFF);
                        i++;
                        length++;
                    } else {
                        /* Single byte character */
                        inputData[length] = inputBytes[i] & 0xFF;
                        length++;
                    }
                }
                infoLine("Using GB2312 character encoding");
                eciMode = 29;
            } else {
                /* GB2312 encoding won't work, use other ECI mode */
                eciProcess(); // Get ECI mode
                length = inputData.length;
            }
        } catch (UnsupportedCharsetException e) {
            throw new OkapiException("Byte conversion encoding error");
        }

        error_number = encodeGridMatrixBinary(length, readerInit);
        if (error_number != 0) {
            throw new OkapiException("Input data too long");
        }

        /* Determine the size of the symbol */
        data_cw = binary.length() / 7;

        auto_layers = 1;
        for (i = 0; i < 13; i++) {
            if (GM_RECOMMEND_CW[i] < data_cw) {
                auto_layers = i + 1;
            }
        }

        min_layers = 13;
        for (i = 12; i > 0; i--) {
            if (GM_MAX_CW[(i - 1)] >= data_cw) {
                min_layers = i;
            }
        }
        layers = auto_layers;
        auto_ecc_level = 3;
        if (layers == 1) {
            auto_ecc_level = 5;
        }
        if ((layers == 2) || (layers == 3)) {
            auto_ecc_level = 4;
        }
        min_ecc_level = 1;
        if (layers == 1) {
            min_ecc_level = 4;
        }
        if ((layers == 2) || (layers == 3)) {
            min_ecc_level = 2;
        }
        ecc_level = auto_ecc_level;

        if ((preferredVersion >= 1) && (preferredVersion <= 13)) {
            input_latch = 1;
            if (preferredVersion > min_layers) {
                layers = preferredVersion;
            } else {
                layers = min_layers;
            }
        }

        if (input_latch == 1) {
            auto_ecc_level = 3;
            if (layers == 1) {
                auto_ecc_level = 5;
            }
            if ((layers == 2) || (layers == 3)) {
                auto_ecc_level = 4;
            }
            ecc_level = auto_ecc_level;
            if (data_cw > GM_DATA_CODEWORDS[(5 * (layers - 1)) + (ecc_level - 1)]) {
                layers++;
            }
        }

        if (input_latch == 0) {
            if ((preferredEccLevel >= 1) && (preferredEccLevel <= 5)) {
                if (preferredEccLevel > min_ecc_level) {
                    ecc_level = preferredEccLevel;
                } else {
                    ecc_level = min_ecc_level;
                }
            }
            if (data_cw > GM_DATA_CODEWORDS[(5 * (layers - 1)) + (ecc_level - 1)]) {
                do {
                    layers++;
                } while ((data_cw > GM_DATA_CODEWORDS[(5 * (layers - 1)) + (ecc_level - 1)]) && (layers <= 13));
            }
        }

        data_max = 1313;
        switch (ecc_level) {
            case 2:
                data_max = 1167;
                break;
            case 3:
                data_max = 1021;
                break;
            case 4:
                data_max = 875;
                break;
            case 5:
                data_max = 729;
                break;
        }

        if (data_cw > data_max) {
            throw new OkapiException("Input data too long");
        }

        addErrorCorrection(data_cw, layers, ecc_level);
        size = 6 + (layers * 12);
        modules = 1 + (layers * 2);

        infoLine("Layers: " + layers);
        infoLine("ECC Level: " + ecc_level);
        infoLine("Data Codewords: " + data_cw);
        infoLine("ECC Codewords: " + GM_DATA_CODEWORDS[((layers - 1) * 5) + (ecc_level - 1)]);
        infoLine("Grid Size: " + modules + " X " + modules);

        grid = new boolean[size * size];

        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                grid[(y * size) + x] = false;
            }
        }

        placeDataInGrid(modules, size);
        addLayerId(size, layers, modules, ecc_level);

        /* Add macromodule frames */
        for (x = 0; x < modules; x++) {
            dark = 1 - (x & 1);
            for (y = 0; y < modules; y++) {
                if (dark == 1) {
                    for (i = 0; i < 5; i++) {
                        grid[((y * 6) * size) + (x * 6) + i] = true;
                        grid[(((y * 6) + 5) * size) + (x * 6) + i] = true;
                        grid[(((y * 6) + i) * size) + (x * 6)] = true;
                        grid[(((y * 6) + i) * size) + (x * 6) + 5] = true;
                    }
                    grid[(((y * 6) + 5) * size) + (x * 6) + 5] = true;
                    dark = 0;
                } else {
                    dark = 1;
                }
            }
        }

        /* Copy values to symbol */
        symbol_width = size;
        row_count = size;
        row_height = new int[row_count];
        pattern = new String[row_count];

        for (x = 0; x < size; x++) {
            bin.setLength(0);
            for (y = 0; y < size; y++) {
                if (grid[(x * size) + y]) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            row_height[x] = 1;
            pattern[x] = bin2pat(bin);
        }
    }

    private int encodeGridMatrixBinary(int length, boolean reader) {
        /* Create a binary stream representation of the input data.
         7 sets are defined - Chinese characters, Numerals, Lower case letters, Upper case letters,
         Mixed numerals and letters, Control characters and 8-bit binary data */
        int sp, glyph = 0;
        Mode current_mode, next_mode, last_mode;
        int c1, c2;
        boolean done;
        int p = 0, ppos;
        int punt = 0;
        int number_pad_posn;
        int byte_count_posn = 0, byte_count = 0;
        int shift, i;
        int[] numbuf = new int[3];
        Mode[] modeMap = calculateModeMap(length);

        binary = new StringBuilder();

        sp = 0;
        current_mode = Mode.NULL;
        number_pad_posn = 0;

        info("Encoding: ");

        if (reader) {
            binary.append("1010"); /* FNC3 - Reader Initialisation */
            info("INIT ");
        }

        if ((eciMode != 3) && (eciMode != 29)) {
            binary.append("1100"); /* ECI */

            if ((eciMode >= 0) && (eciMode <= 1023)) {
                binary.append('0');
                for (i = 0x200; i > 0; i = i >> 1) {
                    if ((eciMode & i) != 0) {
                        binary.append('1');
                    } else {
                        binary.append('0');
                    }
                }
            }

            if ((eciMode >= 1024) && (eciMode <= 32767)) {
                binary.append("10");
                for (i = 0x4000; i > 0; i = i >> 1) {
                    if ((eciMode & i) != 0) {
                        binary.append('1');
                    } else {
                        binary.append('0');
                    }
                }
            }

            if ((eciMode >= 32768) && (eciMode <= 811799)) {
                binary.append("11");
                for (i = 0x80000; i > 0; i = i >> 1) {
                    if ((eciMode & i) != 0) {
                        binary.append('1');
                    } else {
                        binary.append('0');
                    }
                }
            }

            info("ECI ");
            infoSpace(eciMode);
        }

        do {
            next_mode = modeMap[sp];

            if (next_mode != current_mode) {
                switch (current_mode) {
                    case NULL:
                        switch (next_mode) {
                            case GM_CHINESE:
                                binary.append("0001");
                                break;
                            case GM_NUMBER:
                                binary.append("0010");
                                break;
                            case GM_LOWER:
                                binary.append("0011");
                                break;
                            case GM_UPPER:
                                binary.append("0100");
                                break;
                            case GM_MIXED:
                                binary.append("0101");
                                break;
                            case GM_BYTE:
                                binary.append("0111");
                                break;
                        }
                        break;
                    case GM_CHINESE:
                        switch (next_mode) {
                            case GM_NUMBER:
                                binary.append("1111111100001");
                                break; // 8161
                            case GM_LOWER:
                                binary.append("1111111100010");
                                break; // 8162
                            case GM_UPPER:
                                binary.append("1111111100011");
                                break; // 8163
                            case GM_MIXED:
                                binary.append("1111111100100");
                                break; // 8164
                            case GM_BYTE:
                                binary.append("1111111100101");
                                break; // 8165
                        }
                        break;
                    case GM_NUMBER:
                        /* add numeric block padding value */
                        switch (p) {
                            case 1:
                                binary.insert(number_pad_posn, "10");
                                break; // 2 pad digits
                            case 2:
                                binary.insert(number_pad_posn, "01");
                                break; // 1 pad digit
                            case 3:
                                binary.insert(number_pad_posn, "00");
                                break; // 0 pad digits
                        }

                        switch (next_mode) {
                            case GM_CHINESE:
                                binary.append("1111111011");
                                break; // 1019
                            case GM_LOWER:
                                binary.append("1111111100");
                                break; // 1020
                            case GM_UPPER:
                                binary.append("1111111101");
                                break; // 1021
                            case GM_MIXED:
                                binary.append("1111111110");
                                break; // 1022
                            case GM_BYTE:
                                binary.append("1111111111");
                                break; // 1023
                        }
                        break;
                    case GM_LOWER:
                    case GM_UPPER:
                        switch (next_mode) {
                            case GM_CHINESE:
                                binary.append("11100");
                                break; // 28
                            case GM_NUMBER:
                                binary.append("11101");
                                break; // 29
                            case GM_LOWER:
                            case GM_UPPER:
                                binary.append("11110");
                                break; // 30
                            case GM_MIXED:
                                binary.append("1111100");
                                break; // 124
                            case GM_BYTE:
                                binary.append("1111110");
                                break; // 126
                        }
                        break;
                    case GM_MIXED:
                        switch (next_mode) {
                            case GM_CHINESE:
                                binary.append("1111110001");
                                break; // 1009
                            case GM_NUMBER:
                                binary.append("1111110010");
                                break; // 1010
                            case GM_LOWER:
                                binary.append("1111110011");
                                break; // 1011
                            case GM_UPPER:
                                binary.append("1111110100");
                                break; // 1012
                            case GM_BYTE:
                                binary.append("1111110111");
                                break; // 1015
                        }
                        break;
                    case GM_BYTE:
                        /* add byte block length indicator */
                        addByteCount(byte_count_posn, byte_count);
                        byte_count = 0;
                        switch (next_mode) {
                            case GM_CHINESE:
                                binary.append("0001");
                                break; // 1
                            case GM_NUMBER:
                                binary.append("0010");
                                break; // 2
                            case GM_LOWER:
                                binary.append("0011");
                                break; // 3
                            case GM_UPPER:
                                binary.append("0100");
                                break; // 4
                            case GM_MIXED:
                                binary.append("0101");
                                break; // 5
                        }
                        break;
                }

                switch (next_mode) {
                    case GM_CHINESE:
                        info("CHIN ");
                        break;
                    case GM_NUMBER:
                        info("NUMB ");
                        break;
                    case GM_LOWER:
                        info("LOWR ");
                        break;
                    case GM_UPPER:
                        info("UPPR ");
                        break;
                    case GM_MIXED:
                        info("MIXD ");
                        break;
                    case GM_BYTE:
                        info("BYTE ");
                        break;
                }

            }
            last_mode = current_mode;
            current_mode = next_mode;

            switch (current_mode) {
                case GM_CHINESE:
                    done = false;
                    if (inputData[sp] > 0xff) {
                        /* GB2312 character */
                        c1 = (inputData[sp] & 0xff00) >> 8;
                        c2 = inputData[sp] & 0xff;

                        if ((c1 >= 0xa0) && (c1 <= 0xa9)) {
                            glyph = (0x60 * (c1 - 0xa1)) + (c2 - 0xa0);
                        }
                        if ((c1 >= 0xb0) && (c1 <= 0xf7)) {
                            glyph = (0x60 * (c1 - 0xb0 + 9)) + (c2 - 0xa0);
                        }
                        done = true;
                    }
                    if (!(done)) {
                        if (sp != (length - 1)) {
                            if ((inputData[sp] == 13) && (inputData[sp + 1] == 10)) {
                                /* End of Line */
                                glyph = 7776;
                                sp++;
                            }
                            done = true;
                        }
                    }
                    if (!(done)) {
                        if (sp != (length - 1)) {
                            if (((inputData[sp] >= '0') && (inputData[sp] <= '9')) && ((inputData[sp + 1] >= '0') && (inputData[sp + 1] <= '9'))) {
                                /* Two digits */
                                glyph = 8033 + (10 * (inputData[sp] - '0')) + (inputData[sp + 1] - '0');
                                sp++;
                            }
                        }
                    }
                    if (!(done)) {
                        /* Byte value */
                        glyph = 7777 + inputData[sp];
                    }

                    infoSpace(glyph);

                    for (i = 0x1000; i > 0; i = i >> 1) {
                        if ((glyph & i) != 0) {
                            binary.append('1');
                        } else {
                            binary.append('0');
                        }
                    }
                    sp++;
                    break;

                case GM_NUMBER:
                    if (last_mode != current_mode) {
                        /* Reserve a space for numeric digit padding value (2 bits) */
                        number_pad_posn = binary.length();
                    }
                    p = 0;
                    ppos = -1;

                    /* Numeric compression can also include certain combinations of
                     non-numeric character */
                    numbuf[0] = '0';
                    numbuf[1] = '0';
                    numbuf[2] = '0';
                    do {
                        if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) {
                            numbuf[p] = inputData[sp];
                            p++;
                        }
                        switch (inputData[sp]) {
                            case ' ':
                            case '+':
                            case '-':
                            case '.':
                            case ',':
                                punt = inputData[sp];
                                ppos = p;
                                break;
                        }
                        if (sp < (length - 1)) {
                            if ((inputData[sp] == 13) && (inputData[sp + 1] == 10)) {
                                /* <end of line> */
                                punt = inputData[sp];
                                sp++;
                                ppos = p;
                            }
                        }
                        sp++;
                    } while ((p < 3) && (sp < length));

                    if (ppos != -1) {
                        switch (punt) {
                            case ' ':
                                glyph = 0;
                                break;
                            case '+':
                                glyph = 3;
                                break;
                            case '-':
                                glyph = 6;
                                break;
                            case '.':
                                glyph = 9;
                                break;
                            case ',':
                                glyph = 12;
                                break;
                            case 0x13:
                                glyph = 15;
                                break;
                        }
                        glyph += ppos;
                        glyph += 1000;

                        infoSpace(glyph);

                        for (i = 0x200; i > 0; i = i >> 1) {
                            if ((glyph & i) != 0) {
                                binary.append('1');
                            } else {
                                binary.append('0');
                            }
                        }
                    }

                    glyph = (100 * (numbuf[0] - '0')) + (10 * (numbuf[1] - '0')) + (numbuf[2] - '0');
                    infoSpace(glyph);

                    for (i = 0x200; i > 0; i = i >> 1) {
                        if ((glyph & i) != 0) {
                            binary.append('1');
                        } else {
                            binary.append('0');
                        }
                    }
                    break;

                case GM_BYTE:
                    if (last_mode != current_mode) {
                        /* Reserve space for byte block length indicator (9 bits) */
                        byte_count_posn = binary.length();
                    }
                    if (byte_count == 512) {
                        /* Maximum byte block size is 512 bytes. If longer is needed then start a new block */
                        addByteCount(byte_count_posn, byte_count);
                        binary.append("0111");
                        byte_count_posn = binary.length();
                        byte_count = 0;
                    }

                    glyph = inputData[sp];
                    infoSpace(glyph);
                    for (i = 0x80; i > 0; i = i >> 1) {
                        if ((glyph & i) != 0) {
                            binary.append('1');
                        } else {
                            binary.append('0');
                        }
                    }
                    sp++;
                    byte_count++;
                    break;

                case GM_MIXED:
                    shift = 1;
                    if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) {
                        shift = 0;
                    }
                    if ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')) {
                        shift = 0;
                    }
                    if ((inputData[sp] >= 'a') && (inputData[sp] <= 'z')) {
                        shift = 0;
                    }
                    if (inputData[sp] == ' ') {
                        shift = 0;
                    }

                    if (shift == 0) {
                        /* Mixed Mode character */
                        glyph = positionOf((char) inputData[sp], MIXED_ALPHANUM_SET);
                        infoSpace(glyph);

                        for (i = 0x20; i > 0; i = i >> 1) {
                            if ((glyph & i) != 0) {
                                binary.append('1');
                            } else {
                                binary.append('0');
                            }
                        }
                    } else {
                        /* Shift Mode character */
                        binary.append("1111110110"); /* 1014 - shift indicator */

                        addShiftCharacter(inputData[sp]);
                    }

                    sp++;
                    break;

                case GM_UPPER:
                    shift = 1;
                    if ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')) {
                        shift = 0;
                    }
                    if (inputData[sp] == ' ') {
                        shift = 0;
                    }

                    if (shift == 0) {
                        /* Upper Case character */
                        glyph = positionOf((char) inputData[sp], MIXED_ALPHANUM_SET) - 10;
                        if (glyph == 52) {
                            // Space character
                            glyph = 26;
                        }
                        infoSpace(glyph);

                        for (i = 0x10; i > 0; i = i >> 1) {
                            if ((glyph & i) != 0) {
                                binary.append('1');
                            } else {
                                binary.append('0');
                            }
                        }

                    } else {
                        /* Shift Mode character */
                        binary.append("1111101"); /* 127 - shift indicator */

                        addShiftCharacter(inputData[sp]);
                    }

                    sp++;
                    break;

                case GM_LOWER:
                    shift = 1;
                    if ((inputData[sp] >= 'a') && (inputData[sp] <= 'z')) {
                        shift = 0;
                    }
                    if (inputData[sp] == ' ') {
                        shift = 0;
                    }

                    if (shift == 0) {
                        /* Lower Case character */
                        glyph = positionOf((char) inputData[sp], MIXED_ALPHANUM_SET) - 36;
                        infoSpace(glyph);

                        for (i = 0x10; i > 0; i = i >> 1) {
                            if ((glyph & i) != 0) {
                                binary.append('1');
                            } else {
                                binary.append('0');
                            }
                        }

                    } else {
                        /* Shift Mode character */
                        binary.append("1111101"); /* 127 - shift indicator */

                        addShiftCharacter(inputData[sp]);
                    }

                    sp++;
                    break;
            }
            if (binary.length() > 9191) {
                return 1;
            }

        } while (sp < length);

        infoLine();

        if (current_mode == Mode.GM_NUMBER) {
            /* add numeric block padding value */
            switch (p) {
                case 1:
                    binary.insert(number_pad_posn, "10");
                    break; // 2 pad digits
                case 2:
                    binary.insert(number_pad_posn, "01");
                    break; // 1 pad digit
                case 3:
                    binary.insert(number_pad_posn, "00");
                    break; // 0 pad digits
            }
        }

        if (current_mode == Mode.GM_BYTE) {
            /* Add byte block length indicator */
            addByteCount(byte_count_posn, byte_count);
        }

        /* Add "end of data" character */
        switch (current_mode) {
            case GM_CHINESE:
                binary.append("1111111100000");
                break; // 8160
            case GM_NUMBER:
                binary.append("1111111010");
                break; // 1018
            case GM_LOWER:
            case GM_UPPER:
                binary.append("11011");
                break; // 27
            case GM_MIXED:
                binary.append("1111110000");
                break; // 1008
            case GM_BYTE:
                binary.append("0000");
                break; // 0
        }

        /* Add padding bits if required */
        p = 7 - (binary.length() % 7);
        if (p == 7) {
            p = 0;
        }
        for (i = 0; i < p; i++) {
            binary.append('0');
        }

        if (binary.length() > 9191) {
            return 1;
        }

        return 0;
    }

    private Mode[] calculateModeMap(int length) {
        Mode[] modeMap = new Mode[length];
        int i;
        int digitStart, digitLength;
        boolean digits;
        int spaceStart, spaceLength;
        boolean spaces;
        int[] segmentLength;
        Mode[] segmentType;
        int[] segmentStart;
        int segmentCount;

        // Step 1
        // Characters in GB2312 are encoded as Chinese characters
        for (i = 0; i < length; i++) {
            modeMap[i] = Mode.NULL;
            if (inputData[i] > 0xFF) {
                modeMap[i] = Mode.GM_CHINESE;
            }
        }

        // Consecutive <end of line> characters, if preceeded by or followed
        // by chinese characters, are encoded as chinese characters.
        if (length > 3) {
            i = 1;
            do {
                if ((inputData[i] == 13) && (inputData[i + 1] == 10)) {
                    // End of line (CR/LF)

                    if (modeMap[i - 1] == Mode.GM_CHINESE) {
                        modeMap[i] = Mode.GM_CHINESE;
                        modeMap[i + 1] = Mode.GM_CHINESE;
                    }
                    i += 2;
                } else {
                    i++;
                }
            } while (i < length - 1);

            i = length - 3;
            do {
                if ((inputData[i] == 13) && (inputData[i + 1] == 10)) {
                    // End of line (CR/LF)
                    if (modeMap[i + 2] == Mode.GM_CHINESE) {
                        modeMap[i] = Mode.GM_CHINESE;
                        modeMap[i + 1] = Mode.GM_CHINESE;
                    }
                    i -= 2;
                } else {
                    i--;
                }
            } while (i > 0);
        }

        // Digit pairs between chinese characters encode as chinese characters.
        digits = false;
        digitLength = 0;
        digitStart = 0;
        for (i = 1; i < length - 1; i++) {
            if ((inputData[i] >= 48) && (inputData[i] <= 57)) {
                // '0' to '9'
                if (digits == false) {
                    digits = true;
                    digitLength = 1;
                    digitStart = i;
                } else {
                    digitLength++;
                }
            } else {
                if (digits == true) {
                    if ((digitLength % 2) == 0) {
                        if ((modeMap[digitStart - 1] == Mode.GM_CHINESE) &&
                                (modeMap[i] == Mode.GM_CHINESE)) {
                            for(int j = 0; j < digitLength; j++) {
                                modeMap[i - j - 1] = Mode.GM_CHINESE;
                            }
                        }
                    }
                    digits = false;
                }
            }
        }

        // Step 2: all characters 'a' to 'z' are lowercase.
        for (i = 0; i < length; i++) {
            if ((inputData[i] >= 97) && (inputData[i] <= 122)) {
                modeMap[i] = Mode.GM_LOWER;
            }
        }

        // Step 3: all characters 'A' to 'Z' are uppercase.
        for (i = 0; i < length; i++) {
            if ((inputData[i] >= 65) && (inputData[i] <= 90)) {
                modeMap[i] = Mode.GM_UPPER;
            }
        }

        // Step 4: find consecutive <space> characters preceeded or followed
        // by uppercase or lowercase.
        spaces = false;
        spaceLength = 0;
        spaceStart = 0;
        for (i = 1; i < length - 1; i++) {
            if (inputData[i] == 32) {
                if (spaces == false) {
                    spaces = true;
                    spaceLength = 1;
                    spaceStart = i;
                } else {
                    spaceLength++;
                }
            } else {
                if (spaces == true) {

                    Mode modeX = modeMap[spaceStart - 1];
                    Mode modeY = modeMap[i];

                    if ((modeX == Mode.GM_LOWER) || (modeX == Mode.GM_UPPER)) {
                        for (int j = 0; j < spaceLength; j++) {
                            modeMap[i - j - 1] = modeX;
                        }
                    } else {
                        if ((modeY == Mode.GM_LOWER) || (modeY == Mode.GM_UPPER)) {
                            for (int j = 0; j < spaceLength; j++) {
                                modeMap[i - j - 1] = modeY;
                            }
                        }
                    }
                    spaces = false;
                }
            }
        }

        // Step 5: Unassigned characters '0' to '9' are assigned as numerals.
        // Non-numeric characters in table 7 are also assigned as numerals.
        for(i = 0; i < length; i++) {
            if(modeMap[i] == Mode.NULL) {
                if ((inputData[i] >= 48) && (inputData[i] <= 57)) {
                    // '0' to '9'
                    modeMap[i] = Mode.GM_NUMBER;
                } else {
                    switch (inputData[i]) {
                        case 32: // Space
                        case 43: // '+'
                        case 45: // '-'
                        case 46: // "."
                        case 44: // ","
                            modeMap[i] = Mode.GM_NUMBER;
                            break;
                        case 13: // CR
                            if (i < length - 1) {
                                if (inputData[i + 1] == 10) { // LF
                                    // <end of line>
                                    modeMap[i] = Mode.GM_NUMBER;
                                    modeMap[i + 1] = Mode.GM_NUMBER;
                                }
                            }
                    }
                }
            }
        }

        // Step 6: The remining unassigned bytes are assigned as 8-bit binary
        for(i = 0; i < length; i++) {
            if (modeMap[i] == Mode.NULL) {
                modeMap[i] = Mode.GM_BYTE;
            }
        }

        // break into segments
        segmentLength = new int[length];
        segmentType = new Mode[length];
        segmentStart = new int[length];

        segmentCount = 0;
        segmentLength[0] = 1;
        segmentType[0] = modeMap[0];
        segmentStart[0] = 0;
        for (i = 1; i < length; i++) {
            if (modeMap[i] == modeMap[i - 1]) {
                segmentLength[segmentCount]++;
            } else {
                segmentCount++;
                segmentLength[segmentCount] = 1;
                segmentType[segmentCount] = modeMap[i];
                segmentStart[segmentCount] = i;
            }
        }

        // A segment can be a control segment if
        // a) It is not the start segment of the data stream
        // b) All characters are control characters
        // c) The length of the segment is no more than 3
        // d) The previous segment is not chinese
        if (segmentCount > 1) {
            for (i = 1; i < segmentCount; i++) { // (a)
                if ((segmentLength[i] <= 3) && (segmentType[i - 1] != Mode.GM_CHINESE)) { // (c) and (d)
                    boolean controlLatch = true;
                    for (int j = 0; j < segmentLength[i]; j++) {
                        boolean thischarLatch = false;
                        for(int k = 0; k < 63; k++) {
                            if (inputData[segmentStart[i] + j] == SHIFT_SET[k]) {
                                thischarLatch = true;
                            }
                        }

                        if (!(thischarLatch)) {
                            // This character is not a control character
                            controlLatch = false;
                        }
                    }

                    if (controlLatch) { // (b)
                        segmentType[i] = Mode.GM_CONTROL;
                    }
                }
            }
        }

        // Stages 7 to 9
        if (segmentCount >= 3) {
            for (i = 0; i < segmentCount - 1; i++) {
                Mode pm, tm, nm, lm;
                int tl, nl, ll, position;
                boolean lastSegment = false;

                if (i == 0) {
                    pm = Mode.NULL;
                } else {
                    pm = segmentType[i - 1];
                }

                tm = segmentType[i];
                tl = segmentLength[i];

                nm = segmentType[i + 1];
                nl = segmentLength[i + 1];

                lm = segmentType[i + 2];
                ll = segmentLength[i + 2];

                position = segmentStart[i];

                if (i + 2 == segmentCount) {
                    lastSegment = true;
                }

                segmentType[i] = getBestMode(pm, tm, nm, lm, tl, nl, ll, position, lastSegment);

                if (segmentType[i] == Mode.GM_CONTROL) {
                    segmentType[i] = segmentType[i - 1];
                }
            }

            segmentType[i] = appxDnextSection;
            segmentType[i + 1] = appxDlastSection;

            if (segmentType[i] == Mode.GM_CONTROL) {
                segmentType[i] = segmentType[i - 1];
            }
            if (segmentType[i + 1] == Mode.GM_CONTROL) {
                segmentType[i + 1] = segmentType[i];
            }

// Uncomment these lines to override mode selection and generate symbol as shown
// in image D.1 for the test data "AAT2556 电池充电器＋降压转换器 200mA至2A tel:86 019 82512738"
//            segmentType[9] = gmMode.GM_LOWER;
//            segmentType[10] = gmMode.GM_LOWER;
        }

        // Copy segments back to modeMap
        for (i = 0; i < segmentCount; i++) {
            for (int j = 0; j < segmentLength[i]; j++) {
                modeMap[segmentStart[i] + j] = segmentType[i];
            }
        }

        return modeMap;
    }

    private boolean isTransitionValid(Mode previousMode, Mode thisMode) {
        // Filters possible encoding data types from table D.1
        boolean isValid = false;

        switch (previousMode) {
            case GM_CHINESE:
                switch (thisMode) {
                    case GM_CHINESE:
                    case GM_BYTE:
                        isValid = true;
                        break;
                }
                break;
            case GM_NUMBER:
                switch (thisMode) {
                    case GM_NUMBER:
                    case GM_MIXED:
                    case GM_BYTE:
                    case GM_CHINESE:
                        isValid = true;
                        break;
                }
                break;
            case GM_LOWER:
                switch (thisMode) {
                    case GM_LOWER:
                    case GM_MIXED:
                    case GM_BYTE:
                    case GM_CHINESE:
                        isValid = true;
                        break;
                }
                break;
            case GM_UPPER:
                switch (thisMode) {
                    case GM_UPPER:
                    case GM_MIXED:
                    case GM_BYTE:
                    case GM_CHINESE:
                        isValid = true;
                        break;
                }
                break;
            case GM_CONTROL:
                switch (thisMode) {
                    case GM_CONTROL:
                    case GM_BYTE:
                    case GM_CHINESE:
                        isValid = true;
                        break;
                }
                break;
            case GM_BYTE:
                switch (thisMode) {
                    case GM_BYTE:
                    case GM_CHINESE:
                        isValid = true;
                        break;
                }
                break;
        }

        return isValid;
    }

    private Mode intToMode(int input) {
        Mode retVal;

        switch (input) {
            case 1:
                retVal = Mode.GM_CHINESE;
                break;
            case 2:
                retVal = Mode.GM_BYTE;
                break;
            case 3:
                retVal = Mode.GM_CONTROL;
                break;
            case 4:
                retVal = Mode.GM_MIXED;
                break;
            case 5:
                retVal = Mode.GM_UPPER;
                break;
            case 6:
                retVal = Mode.GM_LOWER;
                break;
            case 7:
                retVal = Mode.GM_NUMBER;
                break;
            default:
                retVal = Mode.NULL;
                break;
        }

        return retVal;
    }

    private Mode getBestMode(Mode pm, Mode tm, Mode nm, Mode lm, int tl, int nl, int ll, int position, boolean lastSegment) {
        int tmi, nmi, lmi;
        Mode bestMode = tm;
        int binaryLength;
        int bestBinaryLength = Integer.MAX_VALUE;

        for(tmi = 1; tmi < 8; tmi++) {
            if (isTransitionValid(tm, intToMode(tmi))) {
                for(nmi = 1; nmi < 8; nmi++) {
                    if (isTransitionValid(nm, intToMode(nmi))) {
                        for (lmi = 1; lmi < 8; lmi++) {
                            if (isTransitionValid(lm, intToMode(lmi))) {
                                binaryLength = getBinaryLength(pm, intToMode(tmi), intToMode(nmi), intToMode(lmi), tl, nl, ll, position, lastSegment);
                                if (binaryLength <= bestBinaryLength) {
                                    bestMode = intToMode(tmi);
                                    appxDnextSection = intToMode(nmi);
                                    appxDlastSection = intToMode(lmi);
                                    bestBinaryLength = binaryLength;
                                }
                            }
                        }
                    }
                }
            }
        }

        return bestMode;
    }

    private int getBinaryLength(Mode pm, Mode tm, Mode nm, Mode lm, int tl, int nl, int ll, int position, boolean lastSegment) {

        int binaryLength = getChunkLength(pm, tm, tl, position);
        binaryLength += getChunkLength(tm, nm, nl, (position + tl));
        binaryLength += getChunkLength(nm, lm, ll, (position + tl + nl));

        if (lastSegment) {
            switch (lm) {
                case GM_CHINESE:
                    binaryLength += 13;
                    break;
                case GM_NUMBER:
                    binaryLength += 10;
                    break;
                case GM_LOWER:
                case GM_UPPER:
                    binaryLength += 5;
                    break;
                case GM_MIXED:
                    binaryLength += 10;
                    break;
                case GM_BYTE:
                    binaryLength += 4;
                    break;
            }
        }

        return binaryLength;
    }

    private int getChunkLength(Mode lastMode, Mode thisMode, int thisLength, int position) {
        int byteLength;

        switch (thisMode) {
            case GM_CHINESE:
                byteLength = calcChineseLength(position, thisLength);
                break;
            case GM_NUMBER:
                byteLength = calcNumberLength(position, thisLength);
                break;
            case GM_LOWER:
                byteLength = 5 * thisLength;
                break;
            case GM_UPPER:
                byteLength = 5 * thisLength;
                break;
            case GM_MIXED:
                byteLength = calcMixedLength(position, thisLength);
                break;
            case GM_CONTROL:
                byteLength = 6 * thisLength;
                break;
            default:
            //case GM_BYTE:
                byteLength = calcByteLength(position, thisLength);
                break;
        }

        switch (lastMode) {
            case NULL:
                byteLength += 4;
                break;
            case GM_CHINESE:
                if ((thisMode != Mode.GM_CHINESE) && (thisMode != Mode.GM_CONTROL)) {
                    byteLength += 13;
                }
                break;
            case GM_NUMBER:
                if ((thisMode != Mode.GM_CHINESE) && (thisMode != Mode.GM_CONTROL)) {
                    byteLength += 10;
                }
                break;
            case GM_LOWER:
                switch (thisMode) {
                    case GM_CHINESE:
                    case GM_NUMBER:
                    case GM_UPPER:
                        byteLength += 5;
                        break;
                    case GM_MIXED:
                    case GM_CONTROL:
                    case GM_BYTE:
                        byteLength += 7;
                        break;
                }
                break;
            case GM_UPPER:
                switch (thisMode) {
                    case GM_CHINESE:
                    case GM_NUMBER:
                    case GM_LOWER:
                        byteLength += 5;
                        break;
                    case GM_MIXED:
                    case GM_CONTROL:
                    case GM_BYTE:
                        byteLength += 7;
                        break;
                }
                break;
            case GM_MIXED:
                if (thisMode != Mode.GM_MIXED) {
                    byteLength += 10;
                }
                break;
            case GM_BYTE:
                if (thisMode != Mode.GM_BYTE) {
                    byteLength += 4;
                }
                break;
        }

        if ((lastMode != Mode.GM_BYTE) && (thisMode == Mode.GM_BYTE)) {
            byteLength += 9;
        }

        if ((lastMode != Mode.GM_NUMBER) && (thisMode == Mode.GM_NUMBER)) {
            byteLength += 2;
        }

        return byteLength;
    }

    private int calcChineseLength(int position, int length) {
        int i = 0;
        int bits = 0;

        do {
            bits += 13;

            if (i < length) {
                if ((inputData[position + i] == 13) && (inputData[position + i + 1] == 10)) {
                    // <end of line>
                    i++;
                }

                if (((inputData[position + i] >= 48) && (inputData[position + i] <= 57)) &&
                       ((inputData[position + i + 1] >= 48) && (inputData[position + i + 1] <= 57))) {
                    // two digits
                    i++;
                }
            }
            i++;
        } while (i < length);

        return bits;
    }

    private int calcMixedLength(int position, int length) {
        int bits = 0;
        int i;

        for (i = 0; i < length; i++) {
            bits += 6;
            for(int k = 0; k < 63; k++) {
                if (inputData[position + i] == SHIFT_SET[k]) {
                    bits += 10;
                }
            }
        }

        return bits;
    }

    private int calcNumberLength(int position, int length) {
        int i;
        int bits = 0;
        int numbers = 0;
        int nonnumbers = 0;

        for (i = 0; i < length; i++) {
            if ((inputData[position + i] >= 48) && (inputData[position + i] <= 57)) {
                numbers++;
            } else {
                nonnumbers++;
            }

            if (i != 0) {
                if ((inputData[position + i] == 10) && (inputData[position + i - 1] == 13)) {
                    // <end of line>
                    nonnumbers--;
                }
            }

            if (numbers == 3) {
                if (nonnumbers == 1) {
                    bits += 20;
                } else {
                    bits += 10;
                }
                if (nonnumbers > 1) {
                    // Invalid encoding
                    bits += 100;
                }
                numbers = 0;
                nonnumbers = 0;
            }
        }

        if (numbers > 0) {
            if (nonnumbers == 1) {
                bits += 20;
            } else {
                bits += 10;
            }
        }

        if (nonnumbers > 1) {
            // Invalid
            bits += 100;
        }

        if (!((inputData[position + i - 1] >= 48) && (inputData[position + i - 1] <= 57))) {
            // Data must end with a digit
            bits += 100;
        }


        return bits;
    }

    private int calcByteLength(int position, int length) {
        int i;
        int bits = 0;

        for (i = 0; i < length; i++) {
            if (inputData[position + i] <= 0xFF) {
                bits += 8;
            } else {
                bits += 16;
            }
        }

        return bits;
    }

    private void addByteCount(int byte_count_posn, int byte_count) {
        /* Add the length indicator for byte encoded blocks */
        for (int i = 0; i < 9; i++) {
            if ((byte_count & (0x100 >> i)) != 0) {
                binary.insert(byte_count_posn + i, '0');
            } else {
                binary.insert(byte_count_posn + i, '1');
            }
        }
    }

    void addShiftCharacter(int shifty) {
        /* Add a control character to the data stream */
        int i;
        int glyph = 0;

        for (i = 0; i < 64; i++) {
            if (SHIFT_SET[i] == shifty) {
                glyph = i;
            }
        }

        info("SHT/");
        infoSpace(glyph);

        for (i = 0x20; i > 0; i = i >> 1) {
            if ((glyph & i) != 0) {
                binary.append('1');
            } else {
                binary.append('0');
            }
        }
    }

    private void addErrorCorrection(int data_posn, int layers, int ecc_level) {
        int data_cw, i, j, wp;
        int n1, b1, n2, b2, e1, b3, e2;
        int block_size, data_size, ecc_size;
        int[] data = new int[1320];
        int[] block = new int[130];
        int[] data_block = new int[115];
        int[] ecc_block = new int[70];
        ReedSolomon rs = new ReedSolomon();

        data_cw = GM_DATA_CODEWORDS[((layers - 1) * 5) + (ecc_level - 1)];

        for (i = 0; i < 1320; i++) {
            data[i] = 0;
        }

        /* Convert from binary sream to 7-bit codewords */
        for (i = 0; i < data_posn; i++) {
            for (j = 0; j < 7; j++) {
                if (binary.charAt((i * 7) + j) == '1') {
                    data[i] += 0x40 >> j;
                }
            }
        }

        info("Codewords: ");
        for (i = 0; i < data_posn; i++) {
            infoSpace(data[i]);
        }
        infoLine();

        /* Add padding codewords */
        data[data_posn] = 0x00;
        for (i = (data_posn + 1); i < data_cw; i++) {
            if ((i & 1) != 0) {
                data[i] = 0x7e;
            } else {
                data[i] = 0x00;
            }
        }

        /* Get block sizes */
        n1 = GM_N1[(layers - 1)];
        b1 = GM_B1[(layers - 1)];
        n2 = n1 - 1;
        b2 = GM_B2[(layers - 1)];
        e1 = GM_EBEB[((layers - 1) * 20) + ((ecc_level - 1) * 4)];
        b3 = GM_EBEB[((layers - 1) * 20) + ((ecc_level - 1) * 4) + 1];
        e2 = GM_EBEB[((layers - 1) * 20) + ((ecc_level - 1) * 4) + 2];

        /* Split the data into blocks */
        wp = 0;
        for (i = 0; i < (b1 + b2); i++) {
            if (i < b1) {
                block_size = n1;
            } else {
                block_size = n2;
            }
            if (i < b3) {
                ecc_size = e1;
            } else {
                ecc_size = e2;
            }
            data_size = block_size - ecc_size;

            /* printf("block %d/%d: data %d / ecc %d\n", i + 1, (b1 + b2), data_size, ecc_size);*/
            for (j = 0; j < data_size; j++) {
                data_block[j] = data[wp];
                wp++;
            }

            /* Calculate ECC data for this block */
            rs.init_gf(0x89);
            rs.init_code(ecc_size, 1);
            rs.encode(data_size, data_block);
            for (j = 0; j < ecc_size; j++) {
                ecc_block[j] = rs.getResult(j);
            }

            /* Correct error correction data but in reverse order */
            for (j = 0; j < data_size; j++) {
                block[j] = data_block[j];
            }
            for (j = 0; j < ecc_size; j++) {
                block[(j + data_size)] = ecc_block[ecc_size - j - 1];
            }

            for (j = 0; j < n2; j++) {
                word[((b1 + b2) * j) + i] = block[j];
            }
            if (block_size == n1) {
                word[((b1 + b2) * (n1 - 1)) + i] = block[(n1 - 1)];
            }
        }
    }

    private void placeDataInGrid(int modules, int size) {
        int x, y, macromodule, offset;

        offset = 13 - ((modules - 1) / 2);
        for (y = 0; y < modules; y++) {
            for (x = 0; x < modules; x++) {
                macromodule = GM_MACRO_MATRIX[((y + offset) * 27) + (x + offset)];
                placeMacroModule(x, y, word[macromodule * 2], word[(macromodule * 2) + 1], size);
            }
        }
    }

    void placeMacroModule(int x, int y, int word1, int word2, int size) {
        int i, j;

        i = (x * 6) + 1;
        j = (y * 6) + 1;

        if ((word2 & 0x40) != 0) {
            grid[(j * size) + i + 2] = true;
        }
        if ((word2 & 0x20) != 0) {
            grid[(j * size) + i + 3] = true;
        }
        if ((word2 & 0x10) != 0) {
            grid[((j + 1) * size) + i] = true;
        }
        if ((word2 & 0x08) != 0) {
            grid[((j + 1) * size) + i + 1] = true;
        }
        if ((word2 & 0x04) != 0) {
            grid[((j + 1) * size) + i + 2] = true;
        }
        if ((word2 & 0x02) != 0) {
            grid[((j + 1) * size) + i + 3] = true;
        }
        if ((word2 & 0x01) != 0) {
            grid[((j + 2) * size) + i] = true;
        }
        if ((word1 & 0x40) != 0) {
            grid[((j + 2) * size) + i + 1] = true;
        }
        if ((word1 & 0x20) != 0) {
            grid[((j + 2) * size) + i + 2] = true;
        }
        if ((word1 & 0x10) != 0) {
            grid[((j + 2) * size) + i + 3] = true;
        }
        if ((word1 & 0x08) != 0) {
            grid[((j + 3) * size) + i] = true;
        }
        if ((word1 & 0x04) != 0) {
            grid[((j + 3) * size) + i + 1] = true;
        }
        if ((word1 & 0x02) != 0) {
            grid[((j + 3) * size) + i + 2] = true;
        }
        if ((word1 & 0x01) != 0) {
            grid[((j + 3) * size) + i + 3] = true;
        }
    }

    private void addLayerId(int size, int layers, int modules, int ecc_level) {
        /* Place the layer ID into each macromodule */

        int i, j, layer, start, stop;
        int[] layerid = new int[layers + 1];
        int[] id = new int[modules * modules];


        /* Calculate Layer IDs */
        for (i = 0; i <= layers; i++) {
            if (ecc_level == 1) {
                layerid[i] = 3 - (i % 4);
            } else {
                layerid[i] = (i + 5 - ecc_level) % 4;
            }
        }

        for (i = 0; i < modules; i++) {
            for (j = 0; j < modules; j++) {
                id[(i * modules) + j] = 0;
            }
        }

        /* Calculate which value goes in each macromodule */
        start = modules / 2;
        stop = modules / 2;
        for (layer = 0; layer <= layers; layer++) {
            for (i = start; i <= stop; i++) {
                id[(start * modules) + i] = layerid[layer];
                id[(i * modules) + start] = layerid[layer];
                id[((modules - start - 1) * modules) + i] = layerid[layer];
                id[(i * modules) + (modules - start - 1)] = layerid[layer];
            }
            start--;
            stop++;
        }

        /* Place the data in the grid */
        for (i = 0; i < modules; i++) {
            for (j = 0; j < modules; j++) {
                if ((id[(i * modules) + j] & 0x02) != 0) {
                    grid[(((i * 6) + 1) * size) + (j * 6) + 1] = true;
                }
                if ((id[(i * modules) + j] & 0x01) != 0) {
                    grid[(((i * 6) + 1) * size) + (j * 6) + 2] = true;
                }
            }
        }
    }
}
 public class JapanPost extends Symbol {

    private static final String[] JAPAN_TABLE = {
        "FFT", "FDA", "DFA", "FAD", "FTF", "DAF", "AFD", "ADF", "TFF", "FTT",
        "TFT", "DAT", "DTA", "ADT", "TDA", "ATD", "TAD", "TTF", "FFF"
    };

    private static final char[] KASUT_SET = {
        '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', 'a', 'b', 'c',
        'd', 'e', 'f', 'g', 'h'
    };

    private static final char[] CH_KASUT_SET = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', 'a', 'b', 'c',
        'd', 'e', 'f', 'g', 'h'
    };

    @Override
    protected void encode() {
        String dest;
        String inter;
        int i, sum, check;
        char c;

        content = content.toUpperCase(Locale.ENGLISH);
        if(!content.matches("[0-9A-Z\\-]+")) {
            throw new OkapiException("Invalid characters in data");
        }

        inter = "";

        for (i = 0;
        (i < content.length()) && (inter.length() < 20); i++) {
            c = content.charAt(i);

            if ((c >= '0') && (c <= '9')) {
                inter += c;
            }
            if (c == '-') {
                inter += c;
            }
            if ((c >= 'A') && (c <= 'J')) {
                inter += 'a';
                inter += CH_KASUT_SET[(c - 'A')];
            }

            if ((c >= 'K') && (c <= 'O')) {
                inter += 'b';
                inter += CH_KASUT_SET[(c - 'K')];
            }

            if ((c >= 'U') && (c <= 'Z')) {
                inter += 'c';
                inter += CH_KASUT_SET[(c - 'U')];
            }
        }

        for (i = inter.length(); i < 20; i++) {
            inter += "d";
        }

        dest = "FD";

        sum = 0;
        for (i = 0; i < 20; i++) {
            dest += JAPAN_TABLE[positionOf(inter.charAt(i), KASUT_SET)];
            sum += positionOf(inter.charAt(i), CH_KASUT_SET);
        }

        /* Calculate check digit */
        check = 19 - (sum % 19);
        if (check == 19) {
            check = 0;
        }
        dest += JAPAN_TABLE[positionOf(CH_KASUT_SET[check], KASUT_SET)];
        dest += "DF";

        infoLine("Encoding: " + dest);
        infoLine("Check Digit: " + check);

        readable = "";
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    @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 'A':
                y = 0;
                h = 5;
                break;
            case 'D':
                y = 3;
                h = 5;
                break;
            case 'F':
                y = 0;
                h = 8;
                break;
            case 'T':
                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;
    }
}
 public class KixCode extends Symbol {

    private static final String[] ROYAL_TABLE = {
        "TTFF", "TDAF", "TDFA", "DTAF", "DTFA", "DDAA", "TADF", "TFTF", "TFDA",
        "DATF", "DADA", "DFTA", "TAFD", "TFAD", "TFFT", "DAAD", "DAFT", "DFAT",
        "ATDF", "ADTF", "ADDA", "FTTF", "FTDA", "FDTA", "ATFD", "ADAD", "ADFT",
        "FTAD", "FTFT", "FDAT", "AADD", "AFTD", "AFDT", "FATD", "FADT", "FFTT"
    };

    private static final char[] KR_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'
    };

    @Override
    protected void encode() {

        content = content.toUpperCase(Locale.ENGLISH);

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

        StringBuilder sb = new StringBuilder(content.length());
        for (int i = 0; i < content.length(); i++) {
            int j = positionOf(content.charAt(i), KR_SET);
            sb.append(ROYAL_TABLE[j]);
        }

        String dest = sb.toString();
        infoLine("Encoding: " + dest);

        readable = "";
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    @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++) {
            char c = pattern[0].charAt(xBlock);
            switch (c) {
                case 'A':
                    y = 0;
                    h = 5;
                    break;
                case 'D':
                    y = 3;
                    h = 5;
                    break;
                case 'F':
                    y = 0;
                    h = 8;
                    break;
                case 'T':
                    y = 3;
                    h = 2;
                    break;
                default:
                    throw new IllegalStateException("Unknown pattern character: " + c);
            }
            rectangles.add(new Rectangle2D.Double(x, y, w, h));
            x += 2;
        }
        symbol_width = ((pattern[0].length() - 1) * 2) + 1; // final bar doesn't need extra whitespace
        symbol_height = 8;
    }
}
 public class KoreaPost extends Symbol {

    private static final String[] KOREA_TABLE = {
        "1313150613", "0713131313", "0417131313", "1506131313", "0413171313",
        "17171313", "1315061313", "0413131713", "17131713", "13171713"
    };

    @Override
    protected void encode() {

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

        if (content.length() > 6) {
            throw new OkapiException("Input data too long");
        }

        String padded = "";
        for (int i = 0; i < (6 - content.length()); i++) {
            padded += "0";
        }
        padded += content;

        int total = 0;
        String accumulator = "";
        for (int i = 0; i < padded.length(); i++) {
            int j = Character.getNumericValue(padded.charAt(i));
            accumulator += KOREA_TABLE[j];
            total += j;
        }

        int checkd = 10 - (total % 10);
        if (checkd == 10) {
            checkd = 0;
        }
        infoLine("Check Digit: " + checkd);
        accumulator += KOREA_TABLE[checkd];

        readable = padded + checkd;
        pattern = new String[] { accumulator };
        row_count = 1;
        row_height = new int[] { -1 };
    }
}
 public class Logmars extends Symbol {

    private static final String[] CODE39LM = {
        "1113313111", "3113111131", "1133111131", "3133111111", "1113311131",
        "3113311111", "1133311111", "1113113131", "3113113111", "1133113111",
        "3111131131", "1131131131", "3131131111", "1111331131", "3111331111",
        "1131331111", "1111133131", "3111133111", "1131133111", "1111333111",
        "3111111331", "1131111331", "3131111311", "1111311331", "3111311311",
        "1131311311", "1111113331", "3111113311", "1131113311", "1111313311",
        "3311111131", "1331111131", "3331111111", "1311311131", "3311311111",
        "1331311111", "1311113131", "3311113111", "1331113111", "1313131111",
        "1313111311", "1311131311", "1113131311"
    };

    private static final char[] LOOKUP = {
        '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', '-', '.', ' ', '$', '/', '+',
        '%'
    };

    /** Ratio of wide bar width to narrow bar width. */
    private double moduleWidthRatio = 3;

    /**
     * Sets the ratio of wide bar width to narrow bar width. Valid values are usually
     * between {@code 2} and {@code 3}. The default value is {@code 3}.
     *
     * @param moduleWidthRatio the ratio of wide bar width to narrow bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of wide bar width to narrow bar width.
     *
     * @return the ratio of wide bar width to narrow bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    /** {@inheritDoc} */
    @Override
    protected double getModuleWidth(int originalWidth) {
        if (originalWidth == 1) {
            return 1;
        } else {
            return moduleWidthRatio;
        }
    }

    /** {@inheritDoc} */
    @Override
    protected void encode() {

        if (!content.matches("[0-9A-Z\\. \\-$/+%]*")) {
            throw new OkapiException("Invalid characters in input");
        }

        String p = "";
        int l = content.length();
        int charval, counter = 0;
        char thischar;
        char checkDigit;
        for (int i = 0; i < l; i++) {
            thischar = content.charAt(i);
            charval = positionOf(thischar, LOOKUP);
            counter += charval;
            p += CODE39LM[charval];
        }

        counter = counter % 43;
        checkDigit = LOOKUP[counter];
        infoLine("Check Digit: " + checkDigit);
        p += CODE39LM[counter];

        readable = content + checkDigit;
        pattern = new String[] { "1311313111" + p + "131131311" };
        row_count = 1;
        row_height = new int[] { -1 };
    }
}
 public class MaxiCode extends Symbol {

    /** MaxiCode module sequence, from ISO/IEC 16023 Figure 5 (30 x 33 data grid). */
    private static final int[] MAXICODE_GRID = {
        122, 121, 128, 127, 134, 133, 140, 139, 146, 145, 152, 151, 158, 157, 164, 163, 170, 169, 176, 175, 182, 181, 188, 187, 194, 193, 200, 199, 0,   0,
        124, 123, 130, 129, 136, 135, 142, 141, 148, 147, 154, 153, 160, 159, 166, 165, 172, 171, 178, 177, 184, 183, 190, 189, 196, 195, 202, 201, 817, 0,
        126, 125, 132, 131, 138, 137, 144, 143, 150, 149, 156, 155, 162, 161, 168, 167, 174, 173, 180, 179, 186, 185, 192, 191, 198, 197, 204, 203, 819, 818,
        284, 283, 278, 277, 272, 271, 266, 265, 260, 259, 254, 253, 248, 247, 242, 241, 236, 235, 230, 229, 224, 223, 218, 217, 212, 211, 206, 205, 820, 0,
        286, 285, 280, 279, 274, 273, 268, 267, 262, 261, 256, 255, 250, 249, 244, 243, 238, 237, 232, 231, 226, 225, 220, 219, 214, 213, 208, 207, 822, 821,
        288, 287, 282, 281, 276, 275, 270, 269, 264, 263, 258, 257, 252, 251, 246, 245, 240, 239, 234, 233, 228, 227, 222, 221, 216, 215, 210, 209, 823, 0,
        290, 289, 296, 295, 302, 301, 308, 307, 314, 313, 320, 319, 326, 325, 332, 331, 338, 337, 344, 343, 350, 349, 356, 355, 362, 361, 368, 367, 825, 824,
        292, 291, 298, 297, 304, 303, 310, 309, 316, 315, 322, 321, 328, 327, 334, 333, 340, 339, 346, 345, 352, 351, 358, 357, 364, 363, 370, 369, 826, 0,
        294, 293, 300, 299, 306, 305, 312, 311, 318, 317, 324, 323, 330, 329, 336, 335, 342, 341, 348, 347, 354, 353, 360, 359, 366, 365, 372, 371, 828, 827,
        410, 409, 404, 403, 398, 397, 392, 391, 80,  79,  0,   0,   14,  13,  38,  37,  3,   0,   45,  44,  110, 109, 386, 385, 380, 379, 374, 373, 829, 0,
        412, 411, 406, 405, 400, 399, 394, 393, 82,  81,  41,  0,   16,  15,  40,  39,  4,   0,   0,   46,  112, 111, 388, 387, 382, 381, 376, 375, 831, 830,
        414, 413, 408, 407, 402, 401, 396, 395, 84,  83,  42,  0,   0,   0,   0,   0,   6,   5,   48,  47,  114, 113, 390, 389, 384, 383, 378, 377, 832, 0,
        416, 415, 422, 421, 428, 427, 104, 103, 56,  55,  17,  0,   0,   0,   0,   0,   0,   0,   21,  20,  86,  85,  434, 433, 440, 439, 446, 445, 834, 833,
        418, 417, 424, 423, 430, 429, 106, 105, 58,  57,  0,   0,   0,   0,   0,   0,   0,   0,   23,  22,  88,  87,  436, 435, 442, 441, 448, 447, 835, 0,
        420, 419, 426, 425, 432, 431, 108, 107, 60,  59,  0,   0,   0,   0,   0,   0,   0,   0,   0,   24,  90,  89,  438, 437, 444, 443, 450, 449, 837, 836,
        482, 481, 476, 475, 470, 469, 49,  0,   31,  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   54,  53,  464, 463, 458, 457, 452, 451, 838, 0,
        484, 483, 478, 477, 472, 471, 50,  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   466, 465, 460, 459, 454, 453, 840, 839,
        486, 485, 480, 479, 474, 473, 52,  51,  32,  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   2,   0,   43,  468, 467, 462, 461, 456, 455, 841, 0,
        488, 487, 494, 493, 500, 499, 98,  97,  62,  61,  0,   0,   0,   0,   0,   0,   0,   0,   0,   27,  92,  91,  506, 505, 512, 511, 518, 517, 843, 842,
        490, 489, 496, 495, 502, 501, 100, 99,  64,  63,  0,   0,   0,   0,   0,   0,   0,   0,   29,  28,  94,  93,  508, 507, 514, 513, 520, 519, 844, 0,
        492, 491, 498, 497, 504, 503, 102, 101, 66,  65,  18,  0,   0,   0,   0,   0,   0,   0,   19,  30,  96,  95,  510, 509, 516, 515, 522, 521, 846, 845,
        560, 559, 554, 553, 548, 547, 542, 541, 74,  73,  33,  0,   0,   0,   0,   0,   0,   11,  68,  67,  116, 115, 536, 535, 530, 529, 524, 523, 847, 0,
        562, 561, 556, 555, 550, 549, 544, 543, 76,  75,  0,   0,   8,   7,   36,  35,  12,  0,   70,  69,  118, 117, 538, 537, 532, 531, 526, 525, 849, 848,
        564, 563, 558, 557, 552, 551, 546, 545, 78,  77,  0,   34,  10,  9,   26,  25,  0,   0,   72,  71,  120, 119, 540, 539, 534, 533, 528, 527, 850, 0,
        566, 565, 572, 571, 578, 577, 584, 583, 590, 589, 596, 595, 602, 601, 608, 607, 614, 613, 620, 619, 626, 625, 632, 631, 638, 637, 644, 643, 852, 851,
        568, 567, 574, 573, 580, 579, 586, 585, 592, 591, 598, 597, 604, 603, 610, 609, 616, 615, 622, 621, 628, 627, 634, 633, 640, 639, 646, 645, 853, 0,
        570, 569, 576, 575, 582, 581, 588, 587, 594, 593, 600, 599, 606, 605, 612, 611, 618, 617, 624, 623, 630, 629, 636, 635, 642, 641, 648, 647, 855, 854,
        728, 727, 722, 721, 716, 715, 710, 709, 704, 703, 698, 697, 692, 691, 686, 685, 680, 679, 674, 673, 668, 667, 662, 661, 656, 655, 650, 649, 856, 0,
        730, 729, 724, 723, 718, 717, 712, 711, 706, 705, 700, 699, 694, 693, 688, 687, 682, 681, 676, 675, 670, 669, 664, 663, 658, 657, 652, 651, 858, 857,
        732, 731, 726, 725, 720, 719, 714, 713, 708, 707, 702, 701, 696, 695, 690, 689, 684, 683, 678, 677, 672, 671, 666, 665, 660, 659, 654, 653, 859, 0,
        734, 733, 740, 739, 746, 745, 752, 751, 758, 757, 764, 763, 770, 769, 776, 775, 782, 781, 788, 787, 794, 793, 800, 799, 806, 805, 812, 811, 861, 860,
        736, 735, 742, 741, 748, 747, 754, 753, 760, 759, 766, 765, 772, 771, 778, 777, 784, 783, 790, 789, 796, 795, 802, 801, 808, 807, 814, 813, 862, 0,
        738, 737, 744, 743, 750, 749, 756, 755, 762, 761, 768, 767, 774, 773, 780, 779, 786, 785, 792, 791, 798, 797, 804, 803, 810, 809, 816, 815, 864, 863
    };

    /**
     * ASCII character to Code Set mapping, from ISO/IEC 16023 Appendix A.
     * 1 = Set A, 2 = Set B, 3 = Set C, 4 = Set D, 5 = Set E.
     * 0 refers to special characters that fit into more than one set (e.g. GS).
     */
    private static final int[] MAXICODE_SET = {
        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5, 5, 5, 5,
        5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, 2, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2,
        2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4,
        4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
        5, 4, 5, 5, 5, 5, 5, 5, 4, 5, 3, 4, 3, 5, 5, 4, 4, 3, 3, 3,
        4, 3, 5, 4, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
    };

    /** ASCII character to symbol value, from ISO/IEC 16023 Appendix A. */
    private static final int[] MAXICODE_SYMBOL_CHAR = {
        0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 30, 28, 29, 30, 35, 32, 53, 34, 35, 36, 37, 38, 39,
        40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 37,
        38, 39, 40, 41, 52, 1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 42, 43, 44, 45, 46, 0,  1,  2,  3,
        4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
        24, 25, 26, 32, 54, 34, 35, 36, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 47, 48,
        49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 36,
        37, 37, 38, 39, 40, 41, 42, 43, 38, 44, 37, 39, 38, 45, 46, 40, 41, 39, 40, 41,
        42, 42, 47, 43, 44, 43, 44, 45, 45, 46, 47, 46, 0,  1,  2,  3,  4,  5,  6,  7,
        8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32,
        33, 34, 35, 36, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 33, 34, 35, 36
    };

    private int mode;
    private int structuredAppendPosition = 1;
    private int structuredAppendTotal = 1;
    private String primaryData = "";
    private int[] codewords;
    private int[] set = new int[144];
    private int[] character = new int[144];
    private boolean[][] grid = new boolean[33][30];

    /**
     * Sets the MaxiCode mode to use. Only modes 2 to 6 are supported.
     *
     * @param mode the MaxiCode mode to use
     */
    public void setMode(int mode) {
        if (mode < 2 || mode > 6) {
            throw new IllegalArgumentException("Invalid MaxiCode mode: " + mode);
        }
        this.mode = mode;
    }

    /**
     * Returns the MaxiCode mode being used. Only modes 2 to 6 are supported.
     *
     * @return the MaxiCode mode being used
     */
    public int getMode() {
        return mode;
    }

    /**
     * If this MaxiCode symbol is part of a series of MaxiCode symbols appended in a structured format, this method sets the
     * position of this symbol in the series. Valid values are 1 through 8 inclusive.
     *
     * @param position the position of this MaxiCode symbol in the structured append series
     */
    public void setStructuredAppendPosition(int position) {
        if (position < 1 || position > 8) {
            throw new IllegalArgumentException("Invalid MaxiCode structured append position: " + position);
        }
        this.structuredAppendPosition = position;
    }

    /**
     * Returns the position of this MaxiCode symbol in a series of symbols using structured append. If this symbol is not part of
     * such a series, this method will return <code>1</code>.
     *
     * @return the position of this MaxiCode symbol in a series of symbols using structured append
     */
    public int getStructuredAppendPosition() {
        return structuredAppendPosition;
    }

    /**
     * If this MaxiCode symbol is part of a series of MaxiCode symbols appended in a structured format, this method sets the total
     * number of symbols in the series. Valid values are 1 through 8 inclusive. A value of 1 indicates that this symbol is not
     * part of a structured append series.
     *
     * @param total the total number of MaxiCode symbols in the structured append series
     */
    public void setStructuredAppendTotal(int total) {
        if (total < 1 || total > 8) {
            throw new IllegalArgumentException("Invalid MaxiCode structured append total: " + total);
        }
        this.structuredAppendTotal = total;
    }

    /**
     * Returns the size of the series of MaxiCode symbols using structured append that this symbol is part of. If this symbol is
     * not part of a structured append series, this method will return <code>1</code>.
     *
     * @return size of the series that this symbol is part of
     */
    public int getStructuredAppendTotal() {
        return structuredAppendTotal;
    }

    /**
     * Sets the primary data. Should only be used for modes 2 and 3. Must conform to the following structure:
     *
     * <table>
     *   <tr><th>Characters</th><th>Meaning</th></tr>
     *   <tr><td>1-9</td><td>Postal code data which can consist of up to 9 digits (for mode 2) or up to 6
     *                       alphanumeric characters (for mode 3). Remaining unused characters should be
     *                       filled with the SPACE character (ASCII 32).</td></tr>
     *   <tr><td>10-12</td><td>Three-digit country code according to ISO-3166.</td></tr>
     *   <tr><td>13-15</td><td>Three digit service code. This depends on your parcel courier.</td></tr>
     * </table>
     *
     * @param primary the primary data
     */
    public void setPrimary(String primary) {
        primaryData = primary;
    }

    /**
     * Returns the primary data for this MaxiCode symbol. Should only be used for modes 2 and 3.
     *
     * @return the primary data for this MaxiCode symbol
     */
    public String getPrimary() {
        return primaryData;
    }

    /** {@inheritDoc} */
    @Override
    protected void encode() {

        eciProcess();

        // mode 2 -> mode 3 if postal code isn't strictly numeric
        if (mode == 2) {
            for (int i = 0; i < 10 && i < primaryData.length(); i++) {
                if ((primaryData.charAt(i) < '0') || (primaryData.charAt(i) > '9')) {
                    mode = 3;
                    break;
                }
            }
        }

        // initialize the set and character arrays
        processText();

        // start building the codeword array, starting with a copy of the character data
        // insert primary message if this is a structured carrier message; insert mode otherwise
        codewords = Arrays.copyOf(character, character.length);
        if (mode == 2 || mode == 3) {
            int[] primary = getPrimaryCodewords();
            codewords = insertArray(codewords, 0, primary);
        } else {
            codewords = insertArray(codewords, 0, new int[] { mode });
        }

        // insert structured append flag if necessary
        if (structuredAppendTotal > 1) {

            int[] flag = new int[2];
            flag[0] = 33; // padding
            flag[1] = ((structuredAppendPosition - 1) << 3) | (structuredAppendTotal - 1); // position + total

            int index;
            if (mode == 2 || mode == 3) {
                index = 10; // first two data symbols in the secondary message
            } else {
                index = 1; // first two data symbols in the primary message (first symbol at index 0 isn't a data symbol)
            }

            codewords = insertArray(codewords, index, flag);
        }

        int secondaryMax, secondaryECMax;
        if (mode == 5) {
            // 68 data codewords, 56 error corrections in secondary message
            secondaryMax = 68;
            secondaryECMax = 56;
        } else {
            // 84 data codewords, 40 error corrections in secondary message
            secondaryMax = 84;
            secondaryECMax = 40;
        }

        // truncate data codewords to maximum data space available
        int totalMax = secondaryMax + 10;
        if (codewords.length > totalMax) {
            codewords = Arrays.copyOfRange(codewords, 0, totalMax);
        }

        // insert primary error correction between primary message and secondary message (always EEC)
        int[] primary = Arrays.copyOfRange(codewords, 0, 10);
        int[] primaryCheck = getErrorCorrection(primary, 10);
        codewords = insertArray(codewords, 10, primaryCheck);

        // calculate secondary error correction
        int[] secondary = Arrays.copyOfRange(codewords, 20, codewords.length);
        int[] secondaryOdd = new int[secondary.length / 2];
        int[] secondaryEven = new int[secondary.length / 2];
        for (int i = 0; i < secondary.length; i++) {
            if ((i & 1) != 0) { // odd
                secondaryOdd[(i - 1) / 2] = secondary[i];
            } else { // even
                secondaryEven[i / 2] = secondary[i];
            }
        }
        int[] secondaryECOdd = getErrorCorrection(secondaryOdd, secondaryECMax / 2);
        int[] secondaryECEven = getErrorCorrection(secondaryEven, secondaryECMax / 2);

        // add secondary error correction after secondary message
        codewords = Arrays.copyOf(codewords, codewords.length + secondaryECOdd.length + secondaryECEven.length);
        for (int i = 0; i < secondaryECOdd.length; i++) {
            codewords[20 + secondaryMax + (2 * i) + 1] = secondaryECOdd[i];
        }
        for (int i = 0; i < secondaryECEven.length; i++) {
            codewords[20 + secondaryMax + (2 * i)] = secondaryECEven[i];
        }

        infoLine("Mode: " + mode);
        infoLine("ECC Codewords: " + secondaryECMax);
        info("Codewords: ");
        for (int i = 0; i < codewords.length; i++) {
            infoSpace(codewords[i]);
        }
        infoLine();

        // copy data into symbol grid
        int[] bit_pattern = new int[7];
        for (int i = 0; i < 33; i++) {
            for (int j = 0; j < 30; j++) {

                int block = (MAXICODE_GRID[(i * 30) + j] + 5) / 6;
                int bit = (MAXICODE_GRID[(i * 30) + j] + 5) % 6;

                if (block != 0) {

                    bit_pattern[0] = (codewords[block - 1] & 0x20) >> 5;
                    bit_pattern[1] = (codewords[block - 1] & 0x10) >> 4;
                    bit_pattern[2] = (codewords[block - 1] & 0x8) >> 3;
                    bit_pattern[3] = (codewords[block - 1] & 0x4) >> 2;
                    bit_pattern[4] = (codewords[block - 1] & 0x2) >> 1;
                    bit_pattern[5] = (codewords[block - 1] & 0x1);

                    if (bit_pattern[bit] != 0) {
                        grid[i][j] = true;
                    } else {
                        grid[i][j] = false;
                    }
                }
            }
        }

        // add orientation markings
        grid[0][28] = true;  // top right filler
        grid[0][29] = true;
        grid[9][10] = true;  // top left marker
        grid[9][11] = true;
        grid[10][11] = true;
        grid[15][7] = true;  // left hand marker
        grid[16][8] = true;
        grid[16][20] = true; // right hand marker
        grid[17][20] = true;
        grid[22][10] = true; // bottom left marker
        grid[23][10] = true;
        grid[22][17] = true; // bottom right marker
        grid[23][17] = true;

        // the following is provided for compatibility, but the results are not useful
        row_count = 33;
        readable = "";
        pattern = new String[33];
        row_height = new int[33];
        for (int i = 0; i < 33; i++) {
            StringBuilder bin = new StringBuilder(30);
            for (int j = 0; j < 30; j++) {
                if (grid[i][j]) {
                    bin.append("1");
                } else {
                    bin.append("0");
                }
            }
            pattern[i] = bin2pat(bin);
            row_height[i] = 1;
        }
        symbol_height = 72;
        symbol_width = 74;
    }

    /**
     * Extracts the postal code, country code and service code from the primary data and returns the corresponding primary message
     * codewords.
     *
     * @return the primary message codewords
     */
    private int[] getPrimaryCodewords() {

        assert mode == 2 || mode == 3;

        if (primaryData.length() != 15) {
            throw new OkapiException("Invalid Primary String");
        }

        for (int i = 9; i < 15; i++) { /* check that country code and service are numeric */
            if (primaryData.charAt(i) < '0' || primaryData.charAt(i) > '9') {
                throw new OkapiException("Invalid Primary String");
            }
        }

        String postcode;
        if (mode == 2) {
            postcode = primaryData.substring(0, 9);
            int index = postcode.indexOf(' ');
            if (index != -1) {
                postcode = postcode.substring(0, index);
            }
        } else {
            assert mode == 3;
            postcode = primaryData.substring(0, 6);
        }

        int country = Integer.parseInt(primaryData.substring(9, 12));
        int service = Integer.parseInt(primaryData.substring(12, 15));

        infoLine("Postal Code: " + postcode);
        infoLine("Country Code: " + country);
        infoLine("Service: " + service);

        if (mode == 2) {
            return getMode2PrimaryCodewords(postcode, country, service);
        } else {
            assert mode == 3;
            return getMode3PrimaryCodewords(postcode, country, service);
        }
    }

    /**
     * Returns the primary message codewords for mode 2.
     *
     * @param postcode the postal code
     * @param country the country code
     * @param service the service code
     * @return the primary message, as codewords
     */
    private static int[] getMode2PrimaryCodewords(String postcode, int country, int service) {

        for (int i = 0; i < postcode.length(); i++) {
            if (postcode.charAt(i) < '0' || postcode.charAt(i) > '9') {
                postcode = postcode.substring(0, i);
                break;
            }
        }

        int postcodeNum = Integer.parseInt(postcode);

        int[] primary = new int[10];
        primary[0] = ((postcodeNum & 0x03) << 4) | 2;
        primary[1] = ((postcodeNum & 0xfc) >> 2);
        primary[2] = ((postcodeNum & 0x3f00) >> 8);
        primary[3] = ((postcodeNum & 0xfc000) >> 14);
        primary[4] = ((postcodeNum & 0x3f00000) >> 20);
        primary[5] = ((postcodeNum & 0x3c000000) >> 26) | ((postcode.length() & 0x3) << 4);
        primary[6] = ((postcode.length() & 0x3c) >> 2) | ((country & 0x3) << 4);
        primary[7] = (country & 0xfc) >> 2;
        primary[8] = ((country & 0x300) >> 8) | ((service & 0xf) << 2);
        primary[9] = ((service & 0x3f0) >> 4);

        return primary;
    }

    /**
     * Returns the primary message codewords for mode 3.
     *
     * @param postcode the postal code
     * @param country the country code
     * @param service the service code
     * @return the primary message, as codewords
     */
    private static int[] getMode3PrimaryCodewords(String postcode, int country, int service) {

        int[] postcodeNums = new int[postcode.length()];

        postcode = postcode.toUpperCase();
        for (int i = 0; i < postcodeNums.length; i++) {
            postcodeNums[i] = postcode.charAt(i);
            if (postcode.charAt(i) >= 'A' && postcode.charAt(i) <= 'Z') {
                // (Capital) letters shifted to Code Set A values
                postcodeNums[i] -= 64;
            }
            if (postcodeNums[i] == 27 || postcodeNums[i] == 31 || postcodeNums[i] == 33 || postcodeNums[i] >= 59) {
                // Not a valid postal code character, use space instead
                postcodeNums[i] = 32;
            }
            // Input characters lower than 27 (NUL - SUB) in postal code are interpreted as capital
            // letters in Code Set A (e.g. LF becomes 'J')
        }

        int[] primary = new int[10];
        primary[0] = ((postcodeNums[5] & 0x03) << 4) | 3;
        primary[1] = ((postcodeNums[4] & 0x03) << 4) | ((postcodeNums[5] & 0x3c) >> 2);
        primary[2] = ((postcodeNums[3] & 0x03) << 4) | ((postcodeNums[4] & 0x3c) >> 2);
        primary[3] = ((postcodeNums[2] & 0x03) << 4) | ((postcodeNums[3] & 0x3c) >> 2);
        primary[4] = ((postcodeNums[1] & 0x03) << 4) | ((postcodeNums[2] & 0x3c) >> 2);
        primary[5] = ((postcodeNums[0] & 0x03) << 4) | ((postcodeNums[1] & 0x3c) >> 2);
        primary[6] = ((postcodeNums[0] & 0x3c) >> 2) | ((country & 0x3) << 4);
        primary[7] = (country & 0xfc) >> 2;
        primary[8] = ((country & 0x300) >> 8) | ((service & 0xf) << 2);
        primary[9] = ((service & 0x3f0) >> 4);

        return primary;
    }

    /**
     * Formats text according to Appendix A, populating the {@link #set} and {@link #character} arrays.
     *
     * @return true if the content fits in this symbol and was formatted; false otherwise
     */
    private void processText() {

        int length = content.length();
        int i, j, count, current_set;

        if (length > 138) {
            throw new OkapiException("Input data too long");
        }

        for (i = 0; i < 144; i++) {
            set[i] = -1;
            character[i] = 0;
        }

        for (i = 0; i < length; i++) {
            /* Look up characters in table from Appendix A - this gives
             value and code set for most characters */
            set[i] = MAXICODE_SET[inputData[i]];
            character[i] = MAXICODE_SYMBOL_CHAR[inputData[i]];
        }

        // If a character can be represented in more than one code set, pick which version to use.
        if (set[0] == 0) {
            if (character[0] == 13) {
                character[0] = 0;
            }
            set[0] = 1;
        }

        for (i = 1; i < length; i++) {
            if (set[i] == 0) {
                /* Special character that can be represented in more than one code set. */
                if (character[i] == 13) {
                    /* Carriage Return */
                    set[i] = bestSurroundingSet(i, length, 1, 5);
                    if (set[i] == 5) {
                        character[i] = 13;
                    } else {
                        character[i] = 0;
                    }
                } else if (character[i] == 28) {
                    /* FS */
                    set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
                    if (set[i] == 5) {
                        character[i] = 32;
                    }
                } else if (character[i] == 29) {
                    /* GS */
                    set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
                    if (set[i] == 5) {
                        character[i] = 33;
                    }
                } else if (character[i] == 30) {
                    /* RS */
                    set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
                    if (set[i] == 5) {
                        character[i] = 34;
                    }
                } else if (character[i] == 32) {
                    /* Space */
                    set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
                    if (set[i] == 1) {
                        character[i] = 32;
                    } else if (set[i] == 2) {
                        character[i] = 47;
                    } else {
                        character[i] = 59;
                    }
                } else if (character[i] == 44) {
                    /* Comma */
                    set[i] = bestSurroundingSet(i, length, 1, 2);
                    if (set[i] == 2) {
                        character[i] = 48;
                    }
                } else if (character[i] == 46) {
                    /* Full Stop */
                    set[i] = bestSurroundingSet(i, length, 1, 2);
                    if (set[i] == 2) {
                        character[i] = 49;
                    }
                } else if (character[i] == 47) {
                    /* Slash */
                    set[i] = bestSurroundingSet(i, length, 1, 2);
                    if (set[i] == 2) {
                        character[i] = 50;
                    }
                } else if (character[i] == 58) {
                    /* Colon */
                    set[i] = bestSurroundingSet(i, length, 1, 2);
                    if (set[i] == 2) {
                        character[i] = 51;
                    }
                }
            }
        }

        for (i = length; i < set.length; i++) {
            /* Add the padding */
            if (set[length - 1] == 2) {
                set[i] = 2;
            } else {
                set[i] = 1;
            }
            character[i] = 33;
        }

        /* Find candidates for number compression (not allowed in primary message in modes 2 and 3). */
        if (mode == 2 || mode == 3) {
            j = 9;
        } else {
            j = 0;
        }
        count = 0;
        for (i = j; i < 143; i++) {
            if (set[i] == 1 && character[i] >= 48 && character[i] <= 57) {
                /* Character is a number */
                count++;
            } else {
                count = 0;
            }
            if (count == 9) {
                /* Nine digits in a row can be compressed */
                set[i] = 6;
                set[i - 1] = 6;
                set[i - 2] = 6;
                set[i - 3] = 6;
                set[i - 4] = 6;
                set[i - 5] = 6;
                set[i - 6] = 6;
                set[i - 7] = 6;
                set[i - 8] = 6;
                count = 0;
            }
        }

        /* Add shift and latch characters */
        current_set = 1;
        i = 0;
        do {
            if ((set[i] != current_set) && (set[i] != 6)) {
                switch (set[i]) {
                    case 1:
                        if (i + 1 < set.length && set[i + 1] == 1) {
                            if (i + 2 < set.length && set[i + 2] == 1) {
                                if (i + 3 < set.length && set[i + 3] == 1) {
                                    /* Latch A */
                                    insert(i, 63);
                                    current_set = 1;
                                    length++;
                                    i += 3;
                                } else {
                                    /* 3 Shift A */
                                    insert(i, 57);
                                    length++;
                                    i += 2;
                                }
                            } else {
                                /* 2 Shift A */
                                insert(i, 56);
                                length++;
                                i++;
                            }
                        } else {
                            /* Shift A */
                            insert(i, 59);
                            length++;
                        }
                        break;
                    case 2:
                        if (i + 1 < set.length && set[i + 1] == 2) {
                            /* Latch B */
                            insert(i, 63);
                            current_set = 2;
                            length++;
                            i++;
                        } else {
                            /* Shift B */
                            insert(i, 59);
                            length++;
                        }
                        break;
                    case 3:
                        if (i + 3 < set.length && set[i + 1] == 3 && set[i + 2] == 3 && set[i + 3] == 3) {
                            /* Lock In C */
                            insert(i, 60);
                            insert(i, 60);
                            current_set = 3;
                            length++;
                            i += 3;
                        } else {
                            /* Shift C */
                            insert(i, 60);
                            length++;
                        }
                        break;
                    case 4:
                        if (i + 3 < set.length && set[i + 1] == 4 && set[i + 2] == 4 && set[i + 3] == 4) {
                            /* Lock In D */
                            insert(i, 61);
                            insert(i, 61);
                            current_set = 4;
                            length++;
                            i += 3;
                        } else {
                            /* Shift D */
                            insert(i, 61);
                            length++;
                        }
                        break;
                    case 5:
                        if (i + 3 < set.length && set[i + 1] == 5 && set[i + 2] == 5 && set[i + 3] == 5) {
                            /* Lock In E */
                            insert(i, 62);
                            insert(i, 62);
                            current_set = 5;
                            length++;
                            i += 3;
                        } else {
                            /* Shift E */
                            insert(i, 62);
                            length++;
                        }
                        break;
                    default:
                        throw new OkapiException("Unexpected set " + set[i] + " at index " + i + ".");
                }
                i++;
            }
            i++;
        } while (i < set.length);

        /* Number compression has not been forgotten! It's handled below. */
        i = 0;
        do {
            if (set[i] == 6) {
                /* Number compression */
                int value = 0;
                for (j = 0; j < 9; j++) {
                    value *= 10;
                    value += (character[i + j] - '0');
                }
                character[i] = 31; /* NS */
                character[i + 1] = (value & 0x3f000000) >> 24;
                character[i + 2] = (value & 0xfc0000) >> 18;
                character[i + 3] = (value & 0x3f000) >> 12;
                character[i + 4] = (value & 0xfc0) >> 6;
                character[i + 5] = (value & 0x3f);
                i += 6;
                for (j = i; j < 140; j++) {
                    set[j] = set[j + 3];
                    character[j] = character[j + 3];
                }
                length -= 3;
            } else {
                i++;
            }
        } while (i < set.length);

        /* Inject ECI codes to beginning of data, according to Table 3 */
        if (eciMode != 3) {
            insert(0, 27); // ECI

            if ((eciMode >= 0) && (eciMode <= 31)) {
                insert(1, eciMode & 0x1F);
                length += 2;
            }

            if ((eciMode >= 32) && (eciMode <= 1023)) {
                insert(1, 0x20 + (eciMode >> 6));
                insert(2, eciMode & 0x3F);
                length += 3;
            }

            if ((eciMode >= 1024) && (eciMode <= 32767)) {
                insert(1, 0x30 + (eciMode >> 12));
                insert(2, (eciMode >> 6) & 0x3F);
                insert(3, eciMode & 0x3F);
                length += 4;
            }

            if ((eciMode >= 32768) && (eciMode <= 999999)) {
                insert(1, 0x38 + (eciMode >> 18));
                insert(2, (eciMode >> 12) & 0x3F);
                insert(3, (eciMode >> 6) & 0x3F);
                insert(4, eciMode & 0x3F);
                length += 5;
            }
        }

        /* Make sure we haven't exceeded the maximum data length. */
        int maxLength;
        if (mode == 2 || mode == 3) {
            maxLength = 84;
        } else if (mode == 4 || mode == 6) {
            maxLength = 93;
        } else if (mode == 5) {
            maxLength = 77;
        } else {
            maxLength = 0; // impossible
        }
        if (length > maxLength) {
            throw new OkapiException("Input data too long");
        }
    }

    /**
     * Guesses the best set to use at the specified index by looking at the surrounding sets. In general, characters in
     * lower-numbered sets are more common, so we choose them if we can. If no good surrounding sets can be found, the default
     * value returned is the first value from the valid set.
     *
     * @param index the current index
     * @param length the maximum length to look at
     * @param valid the valid sets for this index
     * @return the best set to use at the specified index
     */
    private int bestSurroundingSet(int index, int length, int... valid) {
        int option1 = set[index - 1];
        if (index + 1 < length) {
            // we have two options to check
            int option2 = set[index + 1];
            if (contains(valid, option1) && contains(valid, option2)) {
                return Math.min(option1, option2);
            } else if (contains(valid, option1)) {
                return option1;
            } else if (contains(valid, option2)) {
                return option2;
            } else {
                return valid[0];
            }
        } else {
            // we only have one option to check
            if (contains(valid, option1)) {
                return option1;
            } else {
                return valid[0];
            }
        }
    }

    /**
     * Moves everything up so that the specified shift or latch character can be inserted.
     *
     * @param position the position beyond which everything needs to be shifted
     * @param c the latch or shift character to insert at the specified position, after everything has been shifted
     */
    private void insert(int position, int c) {
        for (int i = 143; i > position; i--) {
            set[i] = set[i - 1];
            character[i] = character[i - 1];
        }
        character[position] = c;
    }

    /**
     * Returns the error correction codewords for the specified data codewords.
     *
     * @param codewords the codewords that we need error correction codewords for
     * @param ecclen the number of error correction codewords needed
     * @return the error correction codewords for the specified data codewords
     */
    private static int[] getErrorCorrection(int[] codewords, int ecclen) {

        ReedSolomon rs = new ReedSolomon();
        rs.init_gf(0x43);
        rs.init_code(ecclen, 1);
        rs.encode(codewords.length, codewords);

        int[] results = new int[ecclen];
        for (int i = 0; i < ecclen; i++) {
            results[i] = rs.getResult(results.length - 1 - i);
        }

        return results;
    }

    /** {@inheritDoc} */
    @Override
    protected void plotSymbol() {

        // hexagons
        for (int row = 0; row < 33; row++) {
            for (int col = 0; col < 30; col++) {
                if (grid[row][col]) {
                    double x = (2.46 * col) + 1.23;
                    if ((row & 1) != 0) {
                        x += 1.23;
                    }
                    double y = (2.135 * row) + 1.43;
                    hexagons.add(new Hexagon(x, y));
                }
            }
        }

        // circles
        double[] radii = { 10.85, 8.97, 7.10, 5.22, 3.31, 1.43 };
        for (int i = 0; i < radii.length; i++) {
            Ellipse2D.Double circle = new Ellipse2D.Double();
            circle.setFrameFromCenter(35.76, 35.60, 35.76 + radii[i], 35.60 + radii[i]);
            target.add(circle);
        }
    }

    /** {@inheritDoc} */
    @Override
    protected int[] getCodewords() {
        return codewords;
    }
}
 public class MicroQrCode extends Symbol {

    public enum EccMode {
        L, M, Q, H
    }

    private enum qrMode {
        NULL, KANJI, BINARY, ALPHANUM, NUMERIC
    }

    /* Table 5 - Encoding/Decoding table for Alphanumeric mode */
    private static final char[] RHODIUM = {
        '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', ' ', '$', '%', '*', '+', '-',
        '.', '/', ':'
    };

    private static final int[] QR_ANNEX_C1 = {
        /* Micro QR Code format information */
        0x4445, 0x4172, 0x4e2b, 0x4b1c, 0x55ae, 0x5099, 0x5fc0, 0x5af7, 0x6793,
        0x62a4, 0x6dfd, 0x68ca, 0x7678, 0x734f, 0x7c16, 0x7921, 0x06de, 0x03e9,
        0x0cb0, 0x0987, 0x1735, 0x1202, 0x1d5b, 0x186c, 0x2508, 0x203f, 0x2f66,
        0x2a51, 0x34e3, 0x31d4, 0x3e8d, 0x3bba
    };

    private static final int[] MICRO_QR_SIZES = { 11, 13, 15, 17 };

    // user-specified values and settings

    private int preferredVersion;
    private EccMode preferredEccLevel = EccMode.L;

    // internal state calculated when setContent() is called

    private qrMode[] inputMode;
    private StringBuilder binary;
    private int[] binaryCount = new int[4];
    private int[] grid;
    private int[] eval;

    /**
     * <p>Sets the preferred symbol size. This value may be ignored if the
     * data string is too large to fit into the specified symbol. Input
     * values correspond to symbol sizes as shown in the following table.
     *
     * <table>
     * <tbody>
     * <tr><th>Input</th><th>Version</th><th>Symbol Size</th></tr>
     * <tr><td>1    </td><td>M1     </td><td>11 x 11    </td></tr>
     * <tr><td>2    </td><td>M2     </td><td>13 x 13    </td></tr>
     * <tr><td>3    </td><td>M3     </td><td>15 x 15    </td></tr>
     * <tr><td>4    </td><td>M4     </td><td>17 x 17    </td></tr>
     * </tbody>
     * </table>
     *
     * @param version symbol size
     */
    public void setPreferredVersion(int version) {
        if (version < 0 || version > 4) { // TODO: min 1
            throw new IllegalArgumentException("Invalid version: " + version);
        }
        preferredVersion = version;
    }

    /**
     * Returns the preferred symbol size.
     *
     * @return the preferred symbol size
     * @see #setPreferredVersion(int)
     */
    public int getPreferredVersion() {
        return preferredVersion;
    }

    /**
     * <p>Set the amount of symbol space allocated to error correction. Levels are
     * predefined according to the following table:
     *
     * <table>
     * <tbody>
     * <tr><th>ECC Level  </th><th>Error Correction Capacity</th><th>Recovery Capacity</th></tr>
     * <tr><td>L (default)</td><td>Approx 20% of symbol     </td><td>Approx 7%        </td></tr>
     * <tr><td>M          </td><td>Approx 37% of symbol     </td><td>Approx 15%       </td></tr>
     * <tr><td>Q          </td><td>Approx 55% of symbol     </td><td>Approx 25%       </td></tr>
     * <tr><td>H          </td><td>Approx 65% of symbol     </td><td>Approx 30%       </td></tr>
     * </tbody>
     * </table>
     *
     * @param eccMode error correction level
     */
    public void setEccMode(EccMode eccMode) {
        preferredEccLevel = eccMode;
    }

    /**
     * Returns the preferred ECC mode (error correction level).
     *
     * @return the preferred ECC mode
     * @see #setEccMode(EccMode)
     */
    public EccMode getEccMode() {
        return preferredEccLevel;
    }

    @Override
    protected void encode() {
        int i, j, size;
        boolean[] version_valid = new boolean[4];
        int n_count, a_count;
        EccMode ecc_level;
        int version, autoversion;
        int bitmask;
        int format, format_full;
        StringBuilder bin = new StringBuilder();
        boolean byteModeUsed;
        boolean alphanumModeUsed;
        boolean kanjiModeUsed;

        if (content.length() > 35) {
            throw new OkapiException("Input data too long");
        }

        inputCharCheck();

        for (i = 0; i < 4; i++) {
            version_valid[i] = true;
        }

        inputMode = new qrMode[40];
        selectEncodingMode();

        n_count = 0;
        a_count = 0;
        for (i = 0; i < content.length(); i++) {
            if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) {
                n_count++;
            }
            if (isAlphanumeric(content.charAt(i))) {
                a_count++;
            }
        }

        if (a_count == content.length()) {
            /* All data can be encoded in Alphanumeric mode */
            for (i = 0; i < content.length(); i++) {
                inputMode[i] = qrMode.ALPHANUM;
            }
        }

        if (n_count == content.length()) {
            /* All data can be encoded in Numeric mode */
            for (i = 0; i < content.length(); i++) {
                inputMode[i] = qrMode.NUMERIC;
            }
        }

        byteModeUsed = false;
        alphanumModeUsed = false;
        kanjiModeUsed = false;

        for (i = 0; i < content.length(); i++) {
            if (inputMode[i] == qrMode.BINARY) {
                byteModeUsed = true;
            }

            if (inputMode[i] == qrMode.ALPHANUM) {
                alphanumModeUsed = true;
            }

            if (inputMode[i] == qrMode.KANJI) {
                kanjiModeUsed = true;
            }
        }

        getBinaryLength();

        /* Eliminate possible versions depending on type of content */
        if (byteModeUsed) {
            version_valid[0] = false;
            version_valid[1] = false;
        }

        if (alphanumModeUsed) {
            version_valid[0] = false;
        }

        if (kanjiModeUsed) {
            version_valid[0] = false;
            version_valid[1] = false;
        }

        /* Eliminate possible versions depending on length of binary data */
        if (binaryCount[0] > 20) {
            version_valid[0] = false;
        }
        if (binaryCount[1] > 40) {
            version_valid[1] = false;
        }
        if (binaryCount[2] > 84) {
            version_valid[2] = false;
        }
        if (binaryCount[3] > 128) {
            throw new OkapiException("Input data too long");
        }

        /* Eliminate possible versions depending on error correction level specified */
        ecc_level = preferredEccLevel;

        if (ecc_level == EccMode.H) {
            throw new OkapiException("Error correction level H not available");
        }

        if (ecc_level == EccMode.Q) {
            version_valid[0] = false;
            version_valid[1] = false;
            version_valid[2] = false;
            if (binaryCount[3] > 80) {
                throw new OkapiException("Input data too long");
            }
        }

        if (ecc_level == EccMode.M) {
            version_valid[0] = false;
            if (binaryCount[1] > 32) {
                version_valid[1] = false;
            }
            if (binaryCount[2] > 68) {
                version_valid[2] = false;
            }
            if (binaryCount[3] > 112) {
                throw new OkapiException("Input data too long");
            }
        }

        autoversion = 3;
        if (version_valid[2]) {
            autoversion = 2;
        }
        if (version_valid[1]) {
            autoversion = 1;
        }
        if (version_valid[0]) {
            autoversion = 0;
        }

        version = autoversion;
        /* Get version from user */
        if(preferredVersion >= 1 && preferredVersion <= 4) {
            if(preferredVersion - 1 >= autoversion) {
                version = preferredVersion - 1;
            }
        }

        /* If there is enough unused space then increase the error correction level */
        if (version == 3) {
            if (binaryCount[3] <= 112) {
                ecc_level = EccMode.M;
            }
            if (binaryCount[3] <= 80) {
                ecc_level = EccMode.Q;
            }
        }

        if (version == 2 && binaryCount[2] <= 68) {
            ecc_level = EccMode.M;
        }

        if (version == 1 && binaryCount[1] <= 32) {
            ecc_level = EccMode.M;
        }

        binary = new StringBuilder();
        generateBinary(version);
        if (binary.length() > 128) {
            throw new OkapiException("Input data too long");
        }

        switch (version) {
        case 0:
            generateM1Symbol();
            infoLine("Version: M1");
            break;
        case 1:
            generateM2Symbol(ecc_level);
            infoLine("Version: M2");
            infoLine("ECC Level: " + levelToLetter(ecc_level));
            break;
        case 2:
            generateM3Symbol(ecc_level);
            infoLine("Version: M3");
            infoLine("ECC Level: " + levelToLetter(ecc_level));
            break;
        case 3:
            generateM4Symbol(ecc_level);
            infoLine("Version: M4");
            infoLine("ECC Level: " + levelToLetter(ecc_level));
            break;
        }

        size = MICRO_QR_SIZES[version];

        grid = new int[size * size];

        for (i = 0; i < size; i++) {
            for (j = 0; j < size; j++) {
                grid[(i * size) + j] = 0;
            }
        }

        setupBitGrid(size);
        populateBitGrid(size);
        bitmask = applyBitmask(size);

        infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask));

        /* Add format data */
        format = 0;
        switch (version) {
        case 1:
            switch (ecc_level) {
            case L:
                format = 1;
                break;
            case M:
                format = 2;
                break;
            }
            break;
        case 2:
            switch (ecc_level) {
            case L:
                format = 3;
                break;
            case M:
                format = 4;
                break;
            }
            break;
        case 3:
            switch (ecc_level) {
            case L:
                format = 5;
                break;
            case M:
                format = 6;
                break;
            case Q:
                format = 7;
                break;
            }
            break;
        }

        format_full = QR_ANNEX_C1[(format << 2) + bitmask];

        if ((format_full & 0x4000) != 0) {
            grid[(8 * size) + 1] += 0x01;
        }
        if ((format_full & 0x2000) != 0) {
            grid[(8 * size) + 2] += 0x01;
        }
        if ((format_full & 0x1000) != 0) {
            grid[(8 * size) + 3] += 0x01;
        }
        if ((format_full & 0x800) != 0) {
            grid[(8 * size) + 4] += 0x01;
        }
        if ((format_full & 0x400) != 0) {
            grid[(8 * size) + 5] += 0x01;
        }
        if ((format_full & 0x200) != 0) {
            grid[(8 * size) + 6] += 0x01;
        }
        if ((format_full & 0x100) != 0) {
            grid[(8 * size) + 7] += 0x01;
        }
        if ((format_full & 0x80) != 0) {
            grid[(8 * size) + 8] += 0x01;
        }
        if ((format_full & 0x40) != 0) {
            grid[(7 * size) + 8] += 0x01;
        }
        if ((format_full & 0x20) != 0) {
            grid[(6 * size) + 8] += 0x01;
        }
        if ((format_full & 0x10) != 0) {
            grid[(5 * size) + 8] += 0x01;
        }
        if ((format_full & 0x08) != 0) {
            grid[(4 * size) + 8] += 0x01;
        }
        if ((format_full & 0x04) != 0) {
            grid[(3 * size) + 8] += 0x01;
        }
        if ((format_full & 0x02) != 0) {
            grid[(2 * size) + 8] += 0x01;
        }
        if ((format_full & 0x01) != 0) {
            grid[(1 * size) + 8] += 0x01;
        }

        readable = "";
        pattern = new String[size];
        row_count = size;
        row_height = new int[size];
        for (i = 0; i < size; i++) {
            bin.setLength(0);
            for (j = 0; j < size; j++) {
                if ((grid[(i * size) + j] & 0x01) != 0) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            pattern[i] = bin2pat(bin);
            row_height[i] = 1;
        }
    }

    private void inputCharCheck() {
        int qmarkBefore, qmarkAfter;
        int i;
        byte[] temp;

        /* Check that input includes valid characters */

        if (content.matches("[\u0000-\u00FF]+")) {
            /* All characters in ISO 8859-1 */
            return;
        }

        /* Otherwise check for Shift-JIS characters */
        qmarkBefore = 0;
        for (i = 0; i < content.length(); i++) {
            if (content.charAt(i) == '?') {
                qmarkBefore++;
            }
        }

        try {
            temp = content.getBytes("SJIS");
        } catch (UnsupportedEncodingException e) {
            throw new OkapiException("Character encoding error");
        }

        qmarkAfter = 0;
        for (i = 0; i < temp.length; i++) {
            if (temp[i] == '?')  {
                qmarkAfter++;
            }
        }

        /* If these values are the same, conversion was successful */
        if (qmarkBefore != qmarkAfter) {
            throw new OkapiException("Invalid characters in input data");
        }
    }

    private char levelToLetter(EccMode ecc_mode) {
        switch (ecc_mode) {
            case L:
                return 'L';
            case M:
                return 'M';
            case Q:
                return 'Q';
            case H:
                return 'H';
            default:
                return ' ';
        }
    }

    private void selectEncodingMode() {
        int i, j;
        int mlen;
        int length = content.length();

        for (i = 0; i < length; i++) {
            if (content.charAt(i) > 0xff) {
                inputMode[i] = qrMode.KANJI;
            } else {
                inputMode[i] = qrMode.BINARY;
                if (isAlphanumeric(content.charAt(i))) {
                    inputMode[i] = qrMode.ALPHANUM;
                }
                if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) {
                    inputMode[i] = qrMode.NUMERIC;
                }
            }
        }

        /* If less than 6 numeric digits together then don't use numeric mode */
        for (i = 0; i < length; i++) {
            if (inputMode[i] == qrMode.NUMERIC) {
                if (((i != 0) && (inputMode[i - 1] != qrMode.NUMERIC))
                        || (i == 0)) {
                    mlen = 0;
                    while (((mlen + i) < length)
                            && (inputMode[mlen + i] == qrMode.NUMERIC)) {
                        mlen++;
                    }
                    if (mlen < 6) {
                        for (j = 0; j < mlen; j++) {
                            inputMode[i + j] = qrMode.ALPHANUM;
                        }
                    }
                }
            }
        }

        /* If less than 4 alphanumeric characters together then don't use alphanumeric mode */
        for (i = 0; i < length; i++) {
            if (inputMode[i] == qrMode.ALPHANUM) {
                if (((i != 0) && (inputMode[i - 1] != qrMode.ALPHANUM))
                        || (i == 0)) {
                    mlen = 0;
                    while (((mlen + i) < length)
                            && (inputMode[mlen + i] == qrMode.ALPHANUM)) {
                        mlen++;
                    }
                    if (mlen < 6) {
                        for (j = 0; j < mlen; j++) {
                            inputMode[i + j] = qrMode.BINARY;
                        }
                    }
                }
            }
        }
    }

    private boolean isAlphanumeric(char cglyph) {
        /* Returns true if input glyph is in the Alphanumeric set */
        boolean retval = false;

        if ((cglyph >= '0') && (cglyph <= '9')) {
            retval = true;
        }
        if ((cglyph >= 'A') && (cglyph <= 'Z')) {
            retval = true;
        }
        switch (cglyph) {
        case ' ':
        case '$':
        case '%':
        case '*':
        case '+':
        case '-':
        case '.':
        case '/':
        case ':':
            retval = true;
            break;
        }

        return retval;
    }

    private String toBinary(int data, int h) {
        StringBuilder binary = new StringBuilder();
        for (; h != 0; h >>= 1) {
            if ((data & h) != 0) {
                binary.append('1');
            } else {
                binary.append('0');
            }
        }
        return binary.toString();
    }

    private void getBinaryLength() {
        int i;
        qrMode currentMode = qrMode.NULL;
        int blockLength;

        /* Always include a terminator */
        for (i = 0; i < 4; i++) {
            binaryCount[i] = 0;
        }

        for (i = 0; i < content.length(); i++) {
            if(currentMode != inputMode[i]) {

                blockLength = 0;
                do {
                    blockLength++;
                } while (((i + blockLength) < content.length())
                    && (inputMode[i + blockLength] == inputMode[i]));

                switch (inputMode[i]) {
                    case KANJI:
                        binaryCount[2] += 5 + (blockLength * 13);
                        binaryCount[3] += 7 + (blockLength * 13);

                        break;
                    case BINARY:
                        binaryCount[2] += 6 + (blockLength * 8);
                        binaryCount[3] += 8 + (blockLength * 8);
                        break;
                    case ALPHANUM:
                        int alphaLength;

                        if ((blockLength % 2) == 1) {
                            /* Odd length block */
                            alphaLength = ((blockLength - 1) / 2) * 11;
                            alphaLength += 6;
                        } else {
                            /* Even length block */
                            alphaLength = (blockLength / 2) * 11;
                        }

                        binaryCount[1] += 4 + alphaLength;
                        binaryCount[2] += 6 + alphaLength;
                        binaryCount[3] += 8 + alphaLength;
                        break;
                    case NUMERIC:
                        int numLength;

                        switch(blockLength % 3) {
                            case 1:
                                /* one digit left over */
                                numLength = ((blockLength - 1) / 3) * 10;
                                numLength += 4;
                                break;
                            case 2:
                                /* two digits left over */
                                numLength = ((blockLength - 2) / 3) * 10;
                                numLength += 7;
                                break;
                            default:
                                /* blockLength is a multiple of 3 */
                                numLength = (blockLength / 3) * 10;
                                break;
                        }

                        binaryCount[0] += 3 + numLength;
                        binaryCount[1] += 5 + numLength;
                        binaryCount[2] += 7 + numLength;
                        binaryCount[3] += 9 + numLength;
                        break;
                }
                currentMode = inputMode[i];
            }
        }

        /* Add terminator */
        if (binaryCount[1] < 37) {
            binaryCount[1] += 5;
        }

        if (binaryCount[2] < 81) {
            binaryCount[2] += 7;
        }

        if (binaryCount[3] < 125) {
            binaryCount[3] += 9;
        }
    }

    private void generateBinary(int version) {
        int position = 0;
        int blockLength, i;
        qrMode data_block;
        int msb, lsb, prod, jis;
        String oneChar;
        byte[] jisBytes;
        int count, first, second, third;

        info("Encoding: ");

        do {
            data_block = inputMode[position];
            blockLength = 0;
            do {
                blockLength++;
            } while (((blockLength + position) < content.length())
                    && (inputMode[position + blockLength] == data_block));

            switch (data_block) {
            case KANJI:
                /* Kanji mode */
                /* Mode indicator */
                switch (version) {
                case 2:
                    binary.append("11");
                    break;
                case 3:
                    binary.append("011");
                    break;
                }

                /* Character count indicator */
                binary.append(toBinary(blockLength, 1 << version)); /* version = 2..3 */

                info("KANJ (" + blockLength + ") ");

                /* Character representation */
                for (i = 0; i < blockLength; i++) {
                    oneChar = "";
                    oneChar += content.charAt(position + i);

                    /* Convert Unicode input to Shift-JIS */
                    try {
                        jisBytes = oneChar.getBytes("SJIS");
                    } catch (UnsupportedEncodingException e) {
                        throw new OkapiException("Character encoding error");
                    }

                    jis = ((jisBytes[0] & 0xFF) << 8);
                    if (jisBytes.length > 1) {
                        jis += (jisBytes[1] & 0xFF);
                    }

                    if (jis > 0x9fff) {
                        jis -= 0xc140;
                    } else {
                        jis -= 0x8140;
                    }
                    msb = (jis & 0xff00) >> 8;
                    lsb = (jis & 0xff);
                    prod = (msb * 0xc0) + lsb;

                    binary.append(toBinary(prod, 0x1000));

                    infoSpace(prod);
                }

                break;
            case BINARY:
                /* Byte mode */
                /* Mode indicator */
                switch (version) {
                case 2:
                    binary.append("10");
                    break;
                case 3:
                    binary.append("010");
                    break;
                }

                /* Character count indicator */
                binary.append(toBinary(blockLength, 2 << version)); /* version = 2..3 */

                info("BYTE (" + blockLength + ") ");

                /* Character representation */
                for (i = 0; i < blockLength; i++) {
                    int lbyte = content.charAt(position + i);
                    binary.append(toBinary(lbyte, 0x80));
                    infoSpace(lbyte);
                }

                break;
            case ALPHANUM:
                /* Alphanumeric mode */
                /* Mode indicator */
                switch (version) {
                case 1:
                    binary.append("1");
                    break;
                case 2:
                    binary.append("01");
                    break;
                case 3:
                    binary.append("001");
                    break;
                }

                /* Character count indicator */
                binary.append(toBinary(blockLength, 2 << version)); /* version = 1..3 */

                info("ALPH (" + blockLength + ") ");

                /* Character representation */
                i = 0;
                while (i < blockLength) {
                    first = positionOf(content.charAt(position + i), RHODIUM);
                    count = 1;
                    prod = first;

                    if (i + 1 < blockLength) {
                        if (inputMode[position + i + 1] == qrMode.ALPHANUM) {
                            second = positionOf(content.charAt(position + i + 1), RHODIUM);
                            count = 2;
                            prod = (first * 45) + second;
                        }
                    }

                    binary.append(toBinary(prod, 1 << (5 * count))); /* count = 1..2 */

                    infoSpace(prod);

                    i += 2;
                }

                break;
            case NUMERIC:
                /* Numeric mode */
                /* Mode indicator */
                switch (version) {
                case 1:
                    binary.append("0");
                    break;
                case 2:
                    binary.append("00");
                    break;
                case 3:
                    binary.append("000");
                    break;
                }

                /* Character count indicator */
                binary.append(toBinary(blockLength, 4 << version)); /* version = 0..3 */

                info("NUMB (" + blockLength + ") ");

                /* Character representation */
                i = 0;
                while (i < blockLength) {
                    first = Character.getNumericValue(content.charAt(position + i));
                    count = 1;
                    prod = first;

                    if ((i + 1) < blockLength) {
                        if (inputMode[position + i + 1] == qrMode.NUMERIC) {
                            second = Character.getNumericValue(content.charAt(position + i + 1));
                            count = 2;
                            prod = (prod * 10) + second;
                        }
                    }

                    if ((i + 2) < blockLength) {
                        if (inputMode[position + i + 2] == qrMode.NUMERIC) {
                            third = Character.getNumericValue(content.charAt(position + i + 2));
                            count = 3;
                            prod = (prod * 10) + third;
                        }
                    }

                    binary.append(toBinary(prod, 1 << (3 * count))); /* count = 1..3 */

                    infoSpace(prod);

                    i += 3;
                }
                break;
            }

            position += blockLength;
        } while (position < content.length() - 1);

        /* Add terminator */
        switch(version) {
            case 0:
                binary.append("000");
                break;
            case 1:
                if (binary.length() < 37) {
                    binary.append("00000");
                }
                break;
            case 2:
                if (binary.length() < 81) {
                    binary.append("0000000");
                }
                break;
            case 3:
                if (binary.length() < 125) {
                    binary.append("000000000");
                }
                break;
        }

        infoLine();
    }

    private void generateM1Symbol() {
        int i, latch;
        int bits_total, bits_left, remainder;
        int data_codewords, ecc_codewords;
        int[] data_blocks = new int[4];
        int[] ecc_blocks = new int[3];
        ReedSolomon rs = new ReedSolomon();

        bits_total = 20;
        latch = 0;

        /* Manage last (4-bit) block */
        bits_left = bits_total - binary.length();
        if (bits_left <= 4) {
            for (i = 0; i < bits_left; i++) {
                binary.append("0");
            }
            latch = 1;
        }

        if (latch == 0) {
            /* Complete current byte */
            remainder = 8 - (binary.length() % 8);
            if (remainder == 8) {
                remainder = 0;
            }
            for (i = 0; i < remainder; i++) {
                binary.append("0");
            }

            /* Add padding */
            bits_left = bits_total - binary.length();
            if (bits_left > 4) {
                remainder = (bits_left - 4) / 8;
                for (i = 0; i < remainder; i++) {
                    if ((i & 1) != 0) {
                        binary.append("00010001");
                    } else {
                        binary.append("11101100");
                    }
                }
            }
            binary.append("0000");
        }

        data_codewords = 3;
        ecc_codewords = 2;

        /* Copy data into codewords */
        for (i = 0; i < (data_codewords - 1); i++) {
            data_blocks[i] = 0;
            if (binary.charAt(i * 8) == '1') {
                data_blocks[i] += 0x80;
            }
            if (binary.charAt((i * 8) + 1) == '1') {
                data_blocks[i] += 0x40;
            }
            if (binary.charAt((i * 8) + 2) == '1') {
                data_blocks[i] += 0x20;
            }
            if (binary.charAt((i * 8) + 3) == '1') {
                data_blocks[i] += 0x10;
            }
            if (binary.charAt((i * 8) + 4) == '1') {
                data_blocks[i] += 0x08;
            }
            if (binary.charAt((i * 8) + 5) == '1') {
                data_blocks[i] += 0x04;
            }
            if (binary.charAt((i * 8) + 6) == '1') {
                data_blocks[i] += 0x02;
            }
            if (binary.charAt((i * 8) + 7) == '1') {
                data_blocks[i] += 0x01;
            }
        }
        data_blocks[2] = 0;
        if (binary.charAt(16) == '1') {
            data_blocks[2] += 0x08;
        }
        if (binary.charAt(17) == '1') {
            data_blocks[2] += 0x04;
        }
        if (binary.charAt(18) == '1') {
            data_blocks[2] += 0x02;
        }
        if (binary.charAt(19) == '1') {
            data_blocks[2] += 0x01;
        }

        info("Codewords: ");
        for (i = 0; i < data_codewords; i++) {
            infoSpace(data_blocks[i]);
        }
        infoLine();

        /* Calculate Reed-Solomon error codewords */
        rs.init_gf(0x11d);
        rs.init_code(ecc_codewords, 0);
        rs.encode(data_codewords, data_blocks);
        for (i = 0; i < ecc_codewords; i++) {
            ecc_blocks[i] = rs.getResult(i);
        }

        /* Add Reed-Solomon codewords to binary data */
        for (i = 0; i < ecc_codewords; i++) {
            binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
        }
    }

    private void generateM2Symbol(EccMode ecc_mode) {
        int i;
        int bits_total, bits_left, remainder;
        int data_codewords, ecc_codewords;
        int[] data_blocks = new int[6];
        int[] ecc_blocks = new int[7];
        ReedSolomon rs = new ReedSolomon();

        bits_total = 40; // ecc_mode == EccMode.L
        if (ecc_mode == EccMode.M) {
            bits_total = 32;
        }

        /* Complete current byte */
        remainder = 8 - (binary.length() % 8);
        if (remainder == 8) {
            remainder = 0;
        }
        for (i = 0; i < remainder; i++) {
            binary.append("0");
        }

        /* Add padding */
        bits_left = bits_total - binary.length();
        remainder = bits_left / 8;
        for (i = 0; i < remainder; i++) {
            if ((i & 1) != 0) {
                binary.append("00010001");
            } else {
                binary.append("11101100");
            }
        }

        data_codewords = 5;
        ecc_codewords = 5; // ecc_mode == EccMode.L
        if (ecc_mode == EccMode.M) {
            data_codewords = 4;
            ecc_codewords = 6;
        }

        /* Copy data into codewords */
        for (i = 0; i < data_codewords; i++) {
            data_blocks[i] = 0;
            if (binary.charAt(i * 8) == '1') {
                data_blocks[i] += 0x80;
            }
            if (binary.charAt((i * 8) + 1) == '1') {
                data_blocks[i] += 0x40;
            }
            if (binary.charAt((i * 8) + 2) == '1') {
                data_blocks[i] += 0x20;
            }
            if (binary.charAt((i * 8) + 3) == '1') {
                data_blocks[i] += 0x10;
            }
            if (binary.charAt((i * 8) + 4) == '1') {
                data_blocks[i] += 0x08;
            }
            if (binary.charAt((i * 8) + 5) == '1') {
                data_blocks[i] += 0x04;
            }
            if (binary.charAt((i * 8) + 6) == '1') {
                data_blocks[i] += 0x02;
            }
            if (binary.charAt((i * 8) + 7) == '1') {
                data_blocks[i] += 0x01;
            }
        }

        info("Codewords: ");
        for (i = 0; i < data_codewords; i++) {
            infoSpace(data_blocks[i]);
        }
        infoLine();

        /* Calculate Reed-Solomon error codewords */
        rs.init_gf(0x11d);
        rs.init_code(ecc_codewords, 0);
        rs.encode(data_codewords, data_blocks);
        for (i = 0; i < ecc_codewords; i++) {
            ecc_blocks[i] = rs.getResult(i);
        }

        /* Add Reed-Solomon codewords to binary data */
        for (i = 0; i < ecc_codewords; i++) {
            binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
        }
    }

    private void generateM3Symbol(EccMode ecc_mode) {
        int i, latch;
        int bits_total, bits_left, remainder;
        int data_codewords, ecc_codewords;
        int[] data_blocks = new int[12];
        int[] ecc_blocks = new int[12];
        ReedSolomon rs = new ReedSolomon();

        latch = 0;

        bits_total = 84; // ecc_mode == EccMode.L
        if (ecc_mode == EccMode.M) {
            bits_total = 68;
        }

        /* Manage last (4-bit) block */
        bits_left = bits_total - binary.length();
        if (bits_left <= 4) {
            for (i = 0; i < bits_left; i++) {
                binary.append("0");
            }
            latch = 1;
        }

        if (latch == 0) {
            /* Complete current byte */
            remainder = 8 - (binary.length() % 8);
            if (remainder == 8) {
                remainder = 0;
            }
            for (i = 0; i < remainder; i++) {
                binary.append("0");
            }

            /* Add padding */
            bits_left = bits_total - binary.length();
            if (bits_left > 4) {
                remainder = (bits_left - 4) / 8;
                for (i = 0; i < remainder; i++) {
                    if ((i & 1) != 0) {
                        binary.append("00010001");
                    } else {
                        binary.append("11101100");
                    }
                }
            }
            binary.append("0000");
        }

        data_codewords = 11;
        ecc_codewords = 6; // ecc_mode == EccMode.L
        if (ecc_mode == EccMode.M) {
            data_codewords = 9;
            ecc_codewords = 8;
        }

        /* Copy data into codewords */
        for (i = 0; i < (data_codewords - 1); i++) {
            data_blocks[i] = 0;
            if (binary.charAt(i * 8) == '1') {
                data_blocks[i] += 0x80;
            }
            if (binary.charAt((i * 8) + 1) == '1') {
                data_blocks[i] += 0x40;
            }
            if (binary.charAt((i * 8) + 2) == '1') {
                data_blocks[i] += 0x20;
            }
            if (binary.charAt((i * 8) + 3) == '1') {
                data_blocks[i] += 0x10;
            }
            if (binary.charAt((i * 8) + 4) == '1') {
                data_blocks[i] += 0x08;
            }
            if (binary.charAt((i * 8) + 5) == '1') {
                data_blocks[i] += 0x04;
            }
            if (binary.charAt((i * 8) + 6) == '1') {
                data_blocks[i] += 0x02;
            }
            if (binary.charAt((i * 8) + 7) == '1') {
                data_blocks[i] += 0x01;
            }
        }

        if (ecc_mode == EccMode.L) {
            data_blocks[10] = 0;
            if (binary.charAt(80) == '1') {
                data_blocks[10] += 0x08;
            }
            if (binary.charAt(81) == '1') {
                data_blocks[10] += 0x04;
            }
            if (binary.charAt(82) == '1') {
                data_blocks[10] += 0x02;
            }
            if (binary.charAt(83) == '1') {
                data_blocks[10] += 0x01;
            }
        }

        if (ecc_mode == EccMode.M) {
            data_blocks[8] = 0;
            if (binary.charAt(64) == '1') {
                data_blocks[8] += 0x08;
            }
            if (binary.charAt(65) == '1') {
                data_blocks[8] += 0x04;
            }
            if (binary.charAt(66) == '1') {
                data_blocks[8] += 0x02;
            }
            if (binary.charAt(67) == '1') {
                data_blocks[8] += 0x01;
            }
        }

        info("Codewords: ");
        for (i = 0; i < data_codewords; i++) {
            infoSpace(data_blocks[i]);
        }
        infoLine();

        /* Calculate Reed-Solomon error codewords */
        rs.init_gf(0x11d);
        rs.init_code(ecc_codewords, 0);
        rs.encode(data_codewords, data_blocks);
        for (i = 0; i < ecc_codewords; i++) {
            ecc_blocks[i] = rs.getResult(i);
        }

        /* Add Reed-Solomon codewords to binary data */
        for (i = 0; i < ecc_codewords; i++) {
            binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
        }
    }

    private void generateM4Symbol(EccMode ecc_mode) {
        int i;
        int bits_total, bits_left, remainder;
        int data_codewords, ecc_codewords;
        int[] data_blocks = new int[17];
        int[] ecc_blocks = new int[15];
        ReedSolomon rs = new ReedSolomon();

        bits_total = 128; // ecc_mode == EccMode.L
        if (ecc_mode == EccMode.M) {
            bits_total = 112;
        }
        if (ecc_mode == EccMode.Q) {
            bits_total = 80;
        }

        /* Complete current byte */
        remainder = 8 - (binary.length() % 8);
        if (remainder == 8) {
            remainder = 0;
        }
        for (i = 0; i < remainder; i++) {
            binary.append("0");
        }

        /* Add padding */
        bits_left = bits_total - binary.length();
        remainder = bits_left / 8;
        for (i = 0; i < remainder; i++) {
            if ((i & 1) != 0) {
                binary.append("00010001");
            } else {
                binary.append("11101100");
            }
        }

        data_codewords = 16;
        ecc_codewords = 8; // ecc_mode == EccMode.L
        if (ecc_mode == EccMode.M) {
            data_codewords = 14;
            ecc_codewords = 10;
        }
        if (ecc_mode == EccMode.Q) {
            data_codewords = 10;
            ecc_codewords = 14;
        }

        /* Copy data into codewords */
        for (i = 0; i < data_codewords; i++) {
            data_blocks[i] = 0;
            if (binary.charAt(i * 8) == '1') {
                data_blocks[i] += 0x80;
            }
            if (binary.charAt((i * 8) + 1) == '1') {
                data_blocks[i] += 0x40;
            }
            if (binary.charAt((i * 8) + 2) == '1') {
                data_blocks[i] += 0x20;
            }
            if (binary.charAt((i * 8) + 3) == '1') {
                data_blocks[i] += 0x10;
            }
            if (binary.charAt((i * 8) + 4) == '1') {
                data_blocks[i] += 0x08;
            }
            if (binary.charAt((i * 8) + 5) == '1') {
                data_blocks[i] += 0x04;
            }
            if (binary.charAt((i * 8) + 6) == '1') {
                data_blocks[i] += 0x02;
            }
            if (binary.charAt((i * 8) + 7) == '1') {
                data_blocks[i] += 0x01;
            }
        }

        info("Codewords: ");
        for (i = 0; i < data_codewords; i++) {
            infoSpace(data_blocks[i]);
        }
        infoLine();

        /* Calculate Reed-Solomon error codewords */
        rs.init_gf(0x11d);
        rs.init_code(ecc_codewords, 0);
        rs.encode(data_codewords, data_blocks);
        for (i = 0; i < ecc_codewords; i++) {
            ecc_blocks[i] = rs.getResult(i);
        }

        /* Add Reed-Solomon codewords to binary data */
        for (i = 0; i < ecc_codewords; i++) {
            binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
        }
    }

    private void setupBitGrid(int size) {
        int i, toggle = 1;

        /* Add timing patterns */
        for (i = 0; i < size; i++) {
            if (toggle == 1) {
                grid[i] = 0x21;
                grid[(i * size)] = 0x21;
                toggle = 0;
            } else {
                grid[i] = 0x20;
                grid[(i * size)] = 0x20;
                toggle = 1;
            }
        }

        /* Add finder patterns */
        placeFinderPattern(size, 0, 0);

        /* Add separators */
        for (i = 0; i < 7; i++) {
            grid[(7 * size) + i] = 0x10;
            grid[(i * size) + 7] = 0x10;
        }
        grid[(7 * size) + 7] = 0x10;


        /* Reserve space for format information */
        for (i = 0; i < 8; i++) {
            grid[(8 * size) + i] += 0x20;
            grid[(i * size) + 8] += 0x20;
        }
        grid[(8 * size) + 8] += 0x20;
    }

    private void placeFinderPattern(int size, int x, int y) {
        int xp, yp;

        int[] finder = {
            1, 1, 1, 1, 1, 1, 1,
            1, 0, 0, 0, 0, 0, 1,
            1, 0, 1, 1, 1, 0, 1,
            1, 0, 1, 1, 1, 0, 1,
            1, 0, 1, 1, 1, 0, 1,
            1, 0, 0, 0, 0, 0, 1,
            1, 1, 1, 1, 1, 1, 1
        };

        for (xp = 0; xp < 7; xp++) {
            for (yp = 0; yp < 7; yp++) {
                if (finder[xp + (7 * yp)] == 1) {
                    grid[((yp + y) * size) + (xp + x)] = 0x11;
                } else {
                    grid[((yp + y) * size) + (xp + x)] = 0x10;
                }
            }
        }
    }

    private void populateBitGrid(int size) {
        boolean goingUp = true;
        int row = 0; /* right hand side */

        int i, n, x, y;

        n = binary.length();
        y = size - 1;
        i = 0;
        do {
            x = (size - 2) - (row * 2);

            if ((grid[(y * size) + (x + 1)] & 0xf0) == 0) {
                if (binary.charAt(i) == '1') {
                    grid[(y * size) + (x + 1)] = 0x01;
                } else {
                    grid[(y * size) + (x + 1)] = 0x00;
                }
                i++;
            }

            if (i < n) {
                if ((grid[(y * size) + x] & 0xf0) == 0) {
                    if (binary.charAt(i) == '1') {
                        grid[(y * size) + x] = 0x01;
                    } else {
                        grid[(y * size) + x] = 0x00;
                    }
                    i++;
                }
            }

            if (goingUp) {
                y--;
            } else {
                y++;
            }
            if (y == 0) {
                /* reached the top */
                row++;
                y = 1;
                goingUp = false;
            }
            if (y == size) {
                /* reached the bottom */
                row++;
                y = size - 1;
                goingUp = true;
            }
        } while (i < n);
    }

    private int applyBitmask(int size) {
        int x, y;
        int p;
        int local_pattern;
        int[] value = new int[8];
        int best_val, best_pattern;
        int bit;

        int[] mask = new int[size * size];
        eval = new int[size * size];

        /* Perform data masking */
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                mask[(y * size) + x] = 0x00;

                if ((grid[(y * size) + x] & 0xf0) == 0) {
                    if ((y & 1) == 0) {
                        mask[(y * size) + x] += 0x01;
                    }

                    if ((((y / 2) + (x / 3)) & 1) == 0) {
                        mask[(y * size) + x] += 0x02;
                    }

                    if (((((y * x) & 1) + ((y * x) % 3)) & 1) == 0) {
                        mask[(y * size) + x] += 0x04;
                    }

                    if (((((y + x) & 1) + ((y * x) % 3)) & 1) == 0) {
                        mask[(y * size) + x] += 0x08;
                    }
                }
            }
        }


        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                if ((grid[(y * size) + x] & 0x01) != 0) {
                    p = 0xff;
                } else {
                    p = 0x00;
                }

                eval[(y * size) + x] = mask[(y * size) + x] ^ p;
            }
        }

        /* Evaluate result */
        for (local_pattern = 0; local_pattern < 4; local_pattern++) {
            value[local_pattern] = evaluateBitmask(size, local_pattern);
        }

        best_pattern = 0;
        best_val = value[0];
        for (local_pattern = 1; local_pattern < 4; local_pattern++) {
            if (value[local_pattern] > best_val) {
                best_pattern = local_pattern;
                best_val = value[local_pattern];
            }
        }

        /* Apply mask */
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                bit = 0;
                switch (best_pattern) {
                case 0:
                    if ((mask[(y * size) + x] & 0x01) != 0) {
                        bit = 1;
                    }
                    break;
                case 1:
                    if ((mask[(y * size) + x] & 0x02) != 0) {
                        bit = 1;
                    }
                    break;
                case 2:
                    if ((mask[(y * size) + x] & 0x04) != 0) {
                        bit = 1;
                    }
                    break;
                case 3:
                    if ((mask[(y * size) + x] & 0x08) != 0) {
                        bit = 1;
                    }
                    break;
                }
                if (bit == 1) {
                    if ((grid[(y * size) + x] & 0x01) != 0) {
                        grid[(y * size) + x] = 0x00;
                    } else {
                        grid[(y * size) + x] = 0x01;
                    }
                }
            }
        }

        return best_pattern;
    }

    private int evaluateBitmask(int size, int pattern) {
        int sum1, sum2, i, filter = 0, retval;

        switch (pattern) {
        case 0:
            filter = 0x01;
            break;
        case 1:
            filter = 0x02;
            break;
        case 2:
            filter = 0x04;
            break;
        case 3:
            filter = 0x08;
            break;
        }

        sum1 = 0;
        sum2 = 0;
        for (i = 1; i < size; i++) {
            if ((eval[(i * size) + size - 1] & filter) != 0) {
                sum1++;
            }
            if ((eval[((size - 1) * size) + i] & filter) != 0) {
                sum2++;
            }
        }

        if (sum1 <= sum2) {
            retval = (sum1 * 16) + sum2;
        } else {
            retval = (sum2 * 16) + sum1;
        }

        return retval;
    }
} public class MsiPlessey extends Symbol {

    public enum CheckDigit {
        NONE, MOD10, MOD10_MOD10, MOD11, MOD11_MOD10
    }

    private final static String[] MSI_PLESS_TABLE = {
        "12121212", "12121221", "12122112", "12122121", "12211212", "12211221",
        "12212112", "12212121", "21121212", "21121221"
    };

    private CheckDigit checkDigit = CheckDigit.NONE;

    /**
     * Set the check digit scheme to use. Options are: None, Modulo-10,
     * 2 x Modulo-10, Modulo-11 and Modulo-11 &amp; 10.
     *
     * @param checkDigit the type of check digit to add to symbol
     */
    public void setCheckDigit(CheckDigit checkDigit) {
        this.checkDigit = checkDigit;
    }

    /**
     * Returns the check digit scheme being used.
     *
     * @return the check digit scheme being used
     */
    public CheckDigit getCheckDigit() {
        return checkDigit;
    }

    @Override
    protected void encode() {

        String intermediate;
        int length = content.length();
        int i;
        String evenString;
        String oddString;
        String addupString;
        int spacer;
        int addup;
        int weight;
        int checkDigit1;
        int checkDigit2;

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

        intermediate = "21"; // Start
        for (i = 0; i < length; i++) {
            intermediate += MSI_PLESS_TABLE[Character.getNumericValue(content.charAt(i))];
        }

        readable = content;

        if (checkDigit == CheckDigit.MOD10 || checkDigit == CheckDigit.MOD10_MOD10) {
            /* Add Modulo-10 check digit */
            evenString = "";
            oddString = "";

            spacer = content.length() & 1;

            for (i = content.length() - 1; i >= 0; i--) {
                if (spacer == 1) {
                    if ((i & 1) != 0) {
                        evenString = content.charAt(i) + evenString;
                    } else {
                        oddString = content.charAt(i) + oddString;
                    }
                } else {
                    if ((i & 1) != 0) {
                        oddString = content.charAt(i) + oddString;
                    } else {
                        evenString = content.charAt(i) + evenString;
                    }
                }
            }

            if (oddString.length() == 0) {
                addupString = "0";
            } else {
                addupString = Integer.toString(Integer.parseInt(oddString) * 2);
            }

            addupString += evenString;

            addup = 0;
            for(i = 0; i < addupString.length(); i++) {
                addup += addupString.charAt(i) - '0';
            }

            checkDigit1 = 10 - (addup % 10);
            if (checkDigit1 == 10) {
                checkDigit1 = 0;
            }

            intermediate += MSI_PLESS_TABLE[checkDigit1];
            readable += checkDigit1;
        }

        if (checkDigit == CheckDigit.MOD11 || checkDigit == CheckDigit.MOD11_MOD10) {
            /* Add a Modulo-11 check digit */
            weight = 2;
            addup = 0;
            for (i = content.length() - 1; i >= 0; i--) {
                addup += (content.charAt(i) - '0') * weight;
                weight++;

                if (weight == 8) {
                    weight = 2;
                }
            }

            checkDigit1 = 11 - (addup % 11);

            if (checkDigit1 == 11) {
                checkDigit1 = 0;
            }

            readable += checkDigit1;
            if (checkDigit1 == 10) {
                intermediate += MSI_PLESS_TABLE[1];
                intermediate += MSI_PLESS_TABLE[0];
            } else {
                intermediate += MSI_PLESS_TABLE[checkDigit1];
            }
        }

        if (checkDigit == CheckDigit.MOD10_MOD10 || checkDigit == CheckDigit.MOD11_MOD10) {
            /* Add a second Modulo-10 check digit */
            evenString = "";
            oddString = "";

            spacer = readable.length() & 1;

            for (i = readable.length() - 1; i >= 0; i--) {
                if (spacer == 1) {
                    if ((i & 1) != 0) {
                        evenString = readable.charAt(i) + evenString;
                    } else {
                        oddString = readable.charAt(i) + oddString;
                    }
                } else {
                    if ((i & 1) != 0) {
                        oddString = readable.charAt(i) + oddString;
                    } else {
                        evenString = readable.charAt(i) + evenString;
                    }
                }
            }

            if(oddString.length() == 0) {
                addupString = "0";
            } else {
                addupString = Integer.toString(Integer.parseInt(oddString) * 2);
            }

            addupString += evenString;

            addup = 0;
            for(i = 0; i < addupString.length(); i++) {
                addup += addupString.charAt(i) - '0';
            }

            checkDigit2 = 10 - (addup % 10);
            if (checkDigit2 == 10) {
                checkDigit2 = 0;
            }

            intermediate += MSI_PLESS_TABLE[checkDigit2];
            readable += checkDigit2;
        }

        intermediate += "121"; // Stop

        pattern = new String[] { intermediate };
        row_count = 1;
        row_height = new int[] { -1 };
    }
}
 public class Nve18 extends Symbol {

    @Override
    protected void encode() {

        String gs1Equivalent = "";
        int zeroes;
        int count = 0;
        int c, cdigit;
        int p = 0;

        if (content.length() > 17) {
            throw new OkapiException("Input data too long");
        }

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

        // Add leading zeroes
        zeroes = 17 - content.length();
        for(int i = 0; i < zeroes; i++) {
            gs1Equivalent += "0";
        }

        gs1Equivalent += content;

        // Add Modulus-10 check digit
        for (int i = gs1Equivalent.length() - 1; i >= 0; i--) {
            c = Character.getNumericValue(gs1Equivalent.charAt(i));
            if ((p % 2) == 0) {
                c = c * 3;
            }
            count += c;
            p++;
        }
        cdigit = 10 - (count % 10);
        if (cdigit == 10) {
            cdigit = 0;
        }

        infoLine("NVE Check Digit: " + cdigit);

        content = "[00]" + gs1Equivalent + cdigit;

        // Defer to Code 128
        Code128 code128 = new Code128();
        code128.setDataType(DataType.GS1);
        code128.setHumanReadableLocation(humanReadableLocation);
        code128.setContent(content);

        readable = code128.readable;
        pattern = code128.pattern;
        row_count = code128.row_count;
        row_height = code128.row_height;
        symbol_height = code128.symbol_height;
        symbol_width = code128.symbol_width;
        rectangles = code128.rectangles;
        texts = code128.texts;

        info(code128.encodeInfo);
    }
}
 public class Pdf417 extends Symbol {

    public enum Mode {
        /** Normal PDF417. */
        NORMAL,
        /** Truncated PDF417. */
        TRUNCATED,
        /** MicroPDF417. */
        MICRO
    }

    private enum EncodingMode {
        FALSE, TEX, BYT, NUM
    }

    private int[] codeWords = new int[2700];
    private int codeWordCount;
    private Mode symbolMode = Mode.NORMAL;
    private Integer columns;
    private Integer rows;
    private int preferredEccLevel = -1;
    private int structuredAppendFileId = 0;
    private int structuredAppendPosition = 1;
    private int structuredAppendTotal = 1;

    private static final int MAX_NUMERIC_COMPACTION_BLOCK_SIZE = 44;

    private static final int[] COEFRS = {
        /* k = 2 */
        27, 917,

        /* k = 4 */
        522, 568, 723, 809,

        /* k = 8 */
        237, 308, 436, 284, 646, 653, 428, 379,

        /* k = 16 */
        274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,

        /* k = 32 */
        361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517,
        273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410,

        /* k = 64 */
        539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877, 381, 612,
        723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511, 400, 672, 762, 283, 184,
        440, 35, 519, 31, 460, 594, 225, 535, 517, 352, 605, 158, 651, 201, 488, 502,
        648, 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843, 623, 264, 543,

        /* k = 128 */
        521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925, 749, 415,
        822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631, 292, 908, 490, 704,
        516, 258, 457, 907, 594, 723, 674, 292, 272, 96, 684, 432, 686, 606, 860, 569,
        193, 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712, 463, 646, 776,
        171, 491, 297, 763, 156, 732, 95, 270, 447, 90, 507, 48, 228, 821, 808, 898,
        784, 663, 627, 378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616,
        157, 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587, 804, 34,
        211, 330, 539, 297, 827, 865, 37, 517, 834, 315, 550, 86, 801, 4, 108, 539,

        /* k = 256 */
        524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786, 138, 720,
        858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194, 280, 201, 280, 828, 757,
        710, 814, 919, 89, 68, 569, 11, 204, 796, 605, 540, 913, 801, 700, 799, 137,
        439, 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284, 549, 209, 884,
        315, 70, 329, 793, 490, 274, 877, 162, 749, 812, 684, 461, 334, 376, 849, 521,
        307, 291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470,
        637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136, 538, 906, 90,
        2, 290, 743, 199, 655, 903, 329, 49, 802, 580, 355, 588, 188, 462, 10, 134,
        628, 320, 479, 130, 739, 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234,
        722, 384, 177, 752, 607, 640, 455, 193, 689, 707, 805, 641, 48, 60, 732, 621,
        895, 544, 261, 852, 655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528,
        503, 118, 49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550,
        73, 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791, 893, 754,
        605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299, 922, 191, 910, 532,
        609, 829, 189, 20, 167, 29, 872, 449, 83, 402, 41, 656, 505, 579, 481, 173,
        404, 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648, 55, 497, 10,

        /* k = 512 */
        352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380, 350, 492,
        197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88, 87, 193, 352, 781,
        846, 75, 327, 520, 435, 543, 203, 666, 249, 346, 781, 621, 640, 268, 794, 534,
        539, 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858, 916, 552, 41,
        542, 289, 122, 272, 383, 800, 485, 98, 752, 472, 761, 107, 784, 860, 658, 741,
        290, 204, 681, 407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142,
        808, 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516, 258,
        240, 518, 794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328, 596, 786, 303,
        570, 381, 415, 641, 156, 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402,
        40, 708, 575, 162, 864, 229, 65, 861, 841, 512, 164, 477, 221, 92, 358, 785,
        288, 357, 850, 836, 827, 736, 707, 94, 8, 494, 114, 521, 2, 499, 851, 543,
        152, 729, 771, 95, 248, 361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820,
        669, 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578,
        37, 124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578, 911,
        283, 711, 472, 420, 245, 288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408,
        842, 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713, 159, 672, 729,
        624, 59, 193, 417, 158, 209, 563, 564, 343, 693, 109, 608, 563, 365, 181, 772,
        677, 310, 248, 353, 708, 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777,
        618, 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45,
        787, 680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644, 905,
        789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341,
        242, 797, 838, 837, 720, 224, 307, 631, 61, 87, 560, 310, 756, 665, 397, 808,
        851, 309, 473, 795, 378, 31, 647, 915, 459, 806, 590, 731, 425, 216, 548, 249,
        321, 881, 699, 535, 673, 782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791,
        660, 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437,
        375, 273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687, 842,
        37, 357, 720, 742, 330, 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316,
        342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721, 610, 46, 656,
        447, 171, 616, 464, 190, 531, 297, 321, 762, 752, 533, 175, 134, 14, 381, 433,
        717, 45, 111, 20, 596, 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780,
        407, 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768, 223, 849, 647,
        63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263
    };

    private static final String[] CODAGEMC = {
        "urA", "xfs", "ypy", "unk", "xdw", "yoz", "pDA", "uls", "pBk", "eBA",
        "pAs", "eAk", "prA", "uvs", "xhy", "pnk", "utw", "xgz", "fDA", "pls", "fBk", "frA", "pvs",
        "uxy", "fnk", "ptw", "uwz", "fls", "psy", "fvs", "pxy", "ftw", "pwz", "fxy", "yrx", "ufk",
        "xFw", "ymz", "onA", "uds", "xEy", "olk", "ucw", "dBA", "oks", "uci", "dAk", "okg", "dAc",
        "ovk", "uhw", "xaz", "dnA", "ots", "ugy", "dlk", "osw", "ugj", "dks", "osi", "dvk", "oxw",
        "uiz", "dts", "owy", "dsw", "owj", "dxw", "oyz", "dwy", "dwj", "ofA", "uFs", "xCy", "odk",
        "uEw", "xCj", "clA", "ocs", "uEi", "ckk", "ocg", "ckc", "ckE", "cvA", "ohs", "uay", "ctk",
        "ogw", "uaj", "css", "ogi", "csg", "csa", "cxs", "oiy", "cww", "oij", "cwi", "cyy", "oFk",
        "uCw", "xBj", "cdA", "oEs", "uCi", "cck", "oEg", "uCb", "ccc", "oEa", "ccE", "oED", "chk",
        "oaw", "uDj", "cgs", "oai", "cgg", "oab", "cga", "cgD", "obj", "cib", "cFA", "oCs", "uBi",
        "cEk", "oCg", "uBb", "cEc", "oCa", "cEE", "oCD", "cEC", "cas", "cag", "caa", "cCk", "uAr",
        "oBa", "oBD", "cCB", "tfk", "wpw", "yez", "mnA", "tds", "woy", "mlk", "tcw", "woj", "FBA",
        "mks", "FAk", "mvk", "thw", "wqz", "FnA", "mts", "tgy", "Flk", "msw", "Fks", "Fkg", "Fvk",
        "mxw", "tiz", "Fts", "mwy", "Fsw", "Fsi", "Fxw", "myz", "Fwy", "Fyz", "vfA", "xps", "yuy",
        "vdk", "xow", "yuj", "qlA", "vcs", "xoi", "qkk", "vcg", "xob", "qkc", "vca", "mfA", "tFs",
        "wmy", "qvA", "mdk", "tEw", "wmj", "qtk", "vgw", "xqj", "hlA", "Ekk", "mcg", "tEb", "hkk",
        "qsg", "hkc", "EvA", "mhs", "tay", "hvA", "Etk", "mgw", "taj", "htk", "qww", "vij", "hss",
        "Esg", "hsg", "Exs", "miy", "hxs", "Eww", "mij", "hww", "qyj", "hwi", "Eyy", "hyy", "Eyj",
        "hyj", "vFk", "xmw", "ytj", "qdA", "vEs", "xmi", "qck", "vEg", "xmb", "qcc", "vEa", "qcE",
        "qcC", "mFk", "tCw", "wlj", "qhk", "mEs", "tCi", "gtA", "Eck", "vai", "tCb", "gsk", "Ecc",
        "mEa", "gsc", "qga", "mED", "EcC", "Ehk", "maw", "tDj", "gxk", "Egs", "mai", "gws", "qii",
        "mab", "gwg", "Ega", "EgD", "Eiw", "mbj", "gyw", "Eii", "gyi", "Eib", "gyb", "gzj", "qFA",
        "vCs", "xli", "qEk", "vCg", "xlb", "qEc", "vCa", "qEE", "vCD", "qEC", "qEB", "EFA", "mCs",
        "tBi", "ghA", "EEk", "mCg", "tBb", "ggk", "qag", "vDb", "ggc", "EEE", "mCD", "ggE", "qaD",
        "ggC", "Eas", "mDi", "gis", "Eag", "mDb", "gig", "qbb", "gia", "EaD", "giD", "gji", "gjb",
        "qCk", "vBg", "xkr", "qCc", "vBa", "qCE", "vBD", "qCC", "qCB", "ECk", "mBg", "tAr", "gak",
        "ECc", "mBa", "gac", "qDa", "mBD", "gaE", "ECC", "gaC", "ECB", "EDg", "gbg", "gba", "gbD",
        "vAq", "vAn", "qBB", "mAq", "EBE", "gDE", "gDC", "gDB", "lfA", "sps", "wey", "ldk", "sow",
        "ClA", "lcs", "soi", "Ckk", "lcg", "Ckc", "CkE", "CvA", "lhs", "sqy", "Ctk", "lgw", "sqj",
        "Css", "lgi", "Csg", "Csa", "Cxs", "liy", "Cww", "lij", "Cwi", "Cyy", "Cyj", "tpk", "wuw",
        "yhj", "ndA", "tos", "wui", "nck", "tog", "wub", "ncc", "toa", "ncE", "toD", "lFk", "smw",
        "wdj", "nhk", "lEs", "smi", "atA", "Cck", "tqi", "smb", "ask", "ngg", "lEa", "asc", "CcE",
        "asE", "Chk", "law", "snj", "axk", "Cgs", "trj", "aws", "nii", "lab", "awg", "Cga", "awa",
        "Ciw", "lbj", "ayw", "Cii", "ayi", "Cib", "Cjj", "azj", "vpA", "xus", "yxi", "vok", "xug",
        "yxb", "voc", "xua", "voE", "xuD", "voC", "nFA", "tms", "wti", "rhA", "nEk", "xvi", "wtb",
        "rgk", "vqg", "xvb", "rgc", "nEE", "tmD", "rgE", "vqD", "nEB", "CFA", "lCs", "sli", "ahA",
        "CEk", "lCg", "slb", "ixA", "agk", "nag", "tnb", "iwk", "rig", "vrb", "lCD", "iwc", "agE",
        "naD", "iwE", "CEB", "Cas", "lDi", "ais", "Cag", "lDb", "iys", "aig", "nbb", "iyg", "rjb",
        "CaD", "aiD", "Cbi", "aji", "Cbb", "izi", "ajb", "vmk", "xtg", "ywr", "vmc", "xta", "vmE",
        "xtD", "vmC", "vmB", "nCk", "tlg", "wsr", "rak", "nCc", "xtr", "rac", "vna", "tlD", "raE",
        "nCC", "raC", "nCB", "raB", "CCk", "lBg", "skr", "aak", "CCc", "lBa", "iik", "aac", "nDa",
        "lBD", "iic", "rba", "CCC", "iiE", "aaC", "CCB", "aaB", "CDg", "lBr", "abg", "CDa", "ijg",
        "aba", "CDD", "ija", "abD", "CDr", "ijr", "vlc", "xsq", "vlE", "xsn", "vlC", "vlB", "nBc",
        "tkq", "rDc", "nBE", "tkn", "rDE", "vln", "rDC", "nBB", "rDB", "CBc", "lAq", "aDc", "CBE",
        "lAn", "ibc", "aDE", "nBn", "ibE", "rDn", "CBB", "ibC", "aDB", "ibB", "aDq", "ibq", "ibn",
        "xsf", "vkl", "tkf", "nAm", "nAl", "CAo", "aBo", "iDo", "CAl", "aBl", "kpk", "BdA", "kos",
        "Bck", "kog", "seb", "Bcc", "koa", "BcE", "koD", "Bhk", "kqw", "sfj", "Bgs", "kqi", "Bgg",
        "kqb", "Bga", "BgD", "Biw", "krj", "Bii", "Bib", "Bjj", "lpA", "sus", "whi", "lok", "sug",
        "loc", "sua", "loE", "suD", "loC", "BFA", "kms", "sdi", "DhA", "BEk", "svi", "sdb", "Dgk",
        "lqg", "svb", "Dgc", "BEE", "kmD", "DgE", "lqD", "BEB", "Bas", "kni", "Dis", "Bag", "knb",
        "Dig", "lrb", "Dia", "BaD", "Bbi", "Dji", "Bbb", "Djb", "tuk", "wxg", "yir", "tuc", "wxa",
        "tuE", "wxD", "tuC", "tuB", "lmk", "stg", "nqk", "lmc", "sta", "nqc", "tva", "stD", "nqE",
        "lmC", "nqC", "lmB", "nqB", "BCk", "klg", "Dak", "BCc", "str", "bik", "Dac", "lna", "klD",
        "bic", "nra", "BCC", "biE", "DaC", "BCB", "DaB", "BDg", "klr", "Dbg", "BDa", "bjg", "Dba",
        "BDD", "bja", "DbD", "BDr", "Dbr", "bjr", "xxc", "yyq", "xxE", "yyn", "xxC", "xxB", "ttc",
        "wwq", "vvc", "xxq", "wwn", "vvE", "xxn", "vvC", "ttB", "vvB", "llc", "ssq", "nnc", "llE",
        "ssn", "rrc", "nnE", "ttn", "rrE", "vvn", "llB", "rrC", "nnB", "rrB", "BBc", "kkq", "DDc",
        "BBE", "kkn", "bbc", "DDE", "lln", "jjc", "bbE", "nnn", "BBB", "jjE", "rrn", "DDB", "jjC",
        "BBq", "DDq", "BBn", "bbq", "DDn", "jjq", "bbn", "jjn", "xwo", "yyf", "xwm", "xwl", "tso",
        "wwf", "vto", "xwv", "vtm", "tsl", "vtl", "lko", "ssf", "nlo", "lkm", "rno", "nlm", "lkl",
        "rnm", "nll", "rnl", "BAo", "kkf", "DBo", "lkv", "bDo", "DBm", "BAl", "jbo", "bDm", "DBl",
        "jbm", "bDl", "jbl", "DBv", "jbv", "xwd", "vsu", "vst", "nku", "rlu", "rlt", "DAu", "bBu",
        "jDu", "jDt", "ApA", "Aok", "keg", "Aoc", "AoE", "AoC", "Aqs", "Aqg", "Aqa", "AqD", "Ari",
        "Arb", "kuk", "kuc", "sha", "kuE", "shD", "kuC", "kuB", "Amk", "kdg", "Bqk", "kvg", "kda",
        "Bqc", "kva", "BqE", "kvD", "BqC", "AmB", "BqB", "Ang", "kdr", "Brg", "kvr", "Bra", "AnD",
        "BrD", "Anr", "Brr", "sxc", "sxE", "sxC", "sxB", "ktc", "lvc", "sxq", "sgn", "lvE", "sxn",
        "lvC", "ktB", "lvB", "Alc", "Bnc", "AlE", "kcn", "Drc", "BnE", "AlC", "DrE", "BnC", "AlB",
        "DrC", "BnB", "Alq", "Bnq", "Aln", "Drq", "Bnn", "Drn", "wyo", "wym", "wyl", "swo", "txo",
        "wyv", "txm", "swl", "txl", "kso", "sgf", "lto", "swv", "nvo", "ltm", "ksl", "nvm", "ltl",
        "nvl", "Ako", "kcf", "Blo", "ksv", "Dno", "Blm", "Akl", "bro", "Dnm", "Bll", "brm", "Dnl",
        "Akv", "Blv", "Dnv", "brv", "yze", "yzd", "wye", "xyu", "wyd", "xyt", "swe", "twu", "swd",
        "vxu", "twt", "vxt", "kse", "lsu", "ksd", "ntu", "lst", "rvu", "ypk", "zew", "xdA", "yos",
        "zei", "xck", "yog", "zeb", "xcc", "yoa", "xcE", "yoD", "xcC", "xhk", "yqw", "zfj", "utA",
        "xgs", "yqi", "usk", "xgg", "yqb", "usc", "xga", "usE", "xgD", "usC", "uxk", "xiw", "yrj",
        "ptA", "uws", "xii", "psk", "uwg", "xib", "psc", "uwa", "psE", "uwD", "psC", "pxk", "uyw",
        "xjj", "ftA", "pws", "uyi", "fsk", "pwg", "uyb", "fsc", "pwa", "fsE", "pwD", "fxk", "pyw",
        "uzj", "fws", "pyi", "fwg", "pyb", "fwa", "fyw", "pzj", "fyi", "fyb", "xFA", "yms", "zdi",
        "xEk", "ymg", "zdb", "xEc", "yma", "xEE", "ymD", "xEC", "xEB", "uhA", "xas", "yni", "ugk",
        "xag", "ynb", "ugc", "xaa", "ugE", "xaD", "ugC", "ugB", "oxA", "uis", "xbi", "owk", "uig",
        "xbb", "owc", "uia", "owE", "uiD", "owC", "owB", "dxA", "oys", "uji", "dwk", "oyg", "ujb",
        "dwc", "oya", "dwE", "oyD", "dwC", "dys", "ozi", "dyg", "ozb", "dya", "dyD", "dzi", "dzb",
        "xCk", "ylg", "zcr", "xCc", "yla", "xCE", "ylD", "xCC", "xCB", "uak", "xDg", "ylr", "uac",
        "xDa", "uaE", "xDD", "uaC", "uaB", "oik", "ubg", "xDr", "oic", "uba", "oiE", "ubD", "oiC",
        "oiB", "cyk", "ojg", "ubr", "cyc", "oja", "cyE", "ojD", "cyC", "cyB", "czg", "ojr", "cza",
        "czD", "czr", "xBc", "ykq", "xBE", "ykn", "xBC", "xBB", "uDc", "xBq", "uDE", "xBn", "uDC",
        "uDB", "obc", "uDq", "obE", "uDn", "obC", "obB", "cjc", "obq", "cjE", "obn", "cjC", "cjB",
        "cjq", "cjn", "xAo", "ykf", "xAm", "xAl", "uBo", "xAv", "uBm", "uBl", "oDo", "uBv", "oDm",
        "oDl", "cbo", "oDv", "cbm", "cbl", "xAe", "xAd", "uAu", "uAt", "oBu", "oBt", "wpA", "yes",
        "zFi", "wok", "yeg", "zFb", "woc", "yea", "woE", "yeD", "woC", "woB", "thA", "wqs", "yfi",
        "tgk", "wqg", "yfb", "tgc", "wqa", "tgE", "wqD", "tgC", "tgB", "mxA", "tis", "wri", "mwk",
        "tig", "wrb", "mwc", "tia", "mwE", "tiD", "mwC", "mwB", "FxA", "mys", "tji", "Fwk", "myg",
        "tjb", "Fwc", "mya", "FwE", "myD", "FwC", "Fys", "mzi", "Fyg", "mzb", "Fya", "FyD", "Fzi",
        "Fzb", "yuk", "zhg", "hjs", "yuc", "zha", "hbw", "yuE", "zhD", "hDy", "yuC", "yuB", "wmk",
        "ydg", "zEr", "xqk", "wmc", "zhr", "xqc", "yva", "ydD", "xqE", "wmC", "xqC", "wmB", "xqB",
        "tak", "wng", "ydr", "vik", "tac", "wna", "vic", "xra", "wnD", "viE", "taC", "viC", "taB",
        "viB", "mik", "tbg", "wnr", "qyk", "mic", "tba", "qyc", "vja", "tbD", "qyE", "miC", "qyC",
        "miB", "qyB", "Eyk", "mjg", "tbr", "hyk", "Eyc", "mja", "hyc", "qza", "mjD", "hyE", "EyC",
        "hyC", "EyB", "Ezg", "mjr", "hzg", "Eza", "hza", "EzD", "hzD", "Ezr", "ytc", "zgq", "grw",
        "ytE", "zgn", "gny", "ytC", "glz", "ytB", "wlc", "ycq", "xnc", "wlE", "ycn", "xnE", "ytn",
        "xnC", "wlB", "xnB", "tDc", "wlq", "vbc", "tDE", "wln", "vbE", "xnn", "vbC", "tDB", "vbB",
        "mbc", "tDq", "qjc", "mbE", "tDn", "qjE", "vbn", "qjC", "mbB", "qjB", "Ejc", "mbq", "gzc",
        "EjE", "mbn", "gzE", "qjn", "gzC", "EjB", "gzB", "Ejq", "gzq", "Ejn", "gzn", "yso", "zgf",
        "gfy", "ysm", "gdz", "ysl", "wko", "ycf", "xlo", "ysv", "xlm", "wkl", "xll", "tBo", "wkv",
        "vDo", "tBm", "vDm", "tBl", "vDl", "mDo", "tBv", "qbo", "vDv", "qbm", "mDl", "qbl", "Ebo",
        "mDv", "gjo", "Ebm", "gjm", "Ebl", "gjl", "Ebv", "gjv", "yse", "gFz", "ysd", "wke", "xku",
        "wkd", "xkt", "tAu", "vBu", "tAt", "vBt", "mBu", "qDu", "mBt", "qDt", "EDu", "gbu", "EDt",
        "gbt", "ysF", "wkF", "xkh", "tAh", "vAx", "mAx", "qBx", "wek", "yFg", "zCr", "wec", "yFa",
        "weE", "yFD", "weC", "weB", "sqk", "wfg", "yFr", "sqc", "wfa", "sqE", "wfD", "sqC", "sqB",
        "lik", "srg", "wfr", "lic", "sra", "liE", "srD", "liC", "liB", "Cyk", "ljg", "srr", "Cyc",
        "lja", "CyE", "ljD", "CyC", "CyB", "Czg", "ljr", "Cza", "CzD", "Czr", "yhc", "zaq", "arw",
        "yhE", "zan", "any", "yhC", "alz", "yhB", "wdc", "yEq", "wvc", "wdE", "yEn", "wvE", "yhn",
        "wvC", "wdB", "wvB", "snc", "wdq", "trc", "snE", "wdn", "trE", "wvn", "trC", "snB", "trB",
        "lbc", "snq", "njc", "lbE", "snn", "njE", "trn", "njC", "lbB", "njB", "Cjc", "lbq", "azc",
        "CjE", "lbn", "azE", "njn", "azC", "CjB", "azB", "Cjq", "azq", "Cjn", "azn", "zio", "irs",
        "rfy", "zim", "inw", "rdz", "zil", "ily", "ikz", "ygo", "zaf", "afy", "yxo", "ziv", "ivy",
        "adz", "yxm", "ygl", "itz", "yxl", "wco", "yEf", "wto", "wcm", "xvo", "yxv", "wcl", "xvm",
        "wtl", "xvl", "slo", "wcv", "tno", "slm", "vro", "tnm", "sll", "vrm", "tnl", "vrl", "lDo",
        "slv", "nbo", "lDm", "rjo", "nbm", "lDl", "rjm", "nbl", "rjl", "Cbo", "lDv", "ajo", "Cbm",
        "izo", "ajm", "Cbl", "izm", "ajl", "izl", "Cbv", "ajv", "zie", "ifw", "rFz", "zid", "idy",
        "icz", "yge", "aFz", "ywu", "ygd", "ihz", "ywt", "wce", "wsu", "wcd", "xtu", "wst", "xtt",
        "sku", "tlu", "skt", "vnu", "tlt", "vnt", "lBu", "nDu", "lBt", "rbu", "nDt", "rbt", "CDu",
        "abu", "CDt", "iju", "abt", "ijt", "ziF", "iFy", "iEz", "ygF", "ywh", "wcF", "wsh", "xsx",
        "skh", "tkx", "vlx", "lAx", "nBx", "rDx", "CBx", "aDx", "ibx", "iCz", "wFc", "yCq", "wFE",
        "yCn", "wFC", "wFB", "sfc", "wFq", "sfE", "wFn", "sfC", "sfB", "krc", "sfq", "krE", "sfn",
        "krC", "krB", "Bjc", "krq", "BjE", "krn", "BjC", "BjB", "Bjq", "Bjn", "yao", "zDf", "Dfy",
        "yam", "Ddz", "yal", "wEo", "yCf", "who", "wEm", "whm", "wEl", "whl", "sdo", "wEv", "svo",
        "sdm", "svm", "sdl", "svl", "kno", "sdv", "lro", "knm", "lrm", "knl", "lrl", "Bbo", "knv",
        "Djo", "Bbm", "Djm", "Bbl", "Djl", "Bbv", "Djv", "zbe", "bfw", "npz", "zbd", "bdy", "bcz",
        "yae", "DFz", "yiu", "yad", "bhz", "yit", "wEe", "wgu", "wEd", "wxu", "wgt", "wxt", "scu",
        "stu", "sct", "tvu", "stt", "tvt", "klu", "lnu", "klt", "nru", "lnt", "nrt", "BDu", "Dbu",
        "BDt", "bju", "Dbt", "bjt", "jfs", "rpy", "jdw", "roz", "jcy", "jcj", "zbF", "bFy", "zjh",
        "jhy", "bEz", "jgz", "yaF", "yih", "yyx", "wEF", "wgh", "wwx", "xxx", "sch", "ssx", "ttx",
        "vvx", "kkx", "llx", "nnx", "rrx", "BBx", "DDx", "bbx", "jFw", "rmz", "jEy", "jEj", "bCz",
        "jaz", "jCy", "jCj", "jBj", "wCo", "wCm", "wCl", "sFo", "wCv", "sFm", "sFl", "kfo", "sFv",
        "kfm", "kfl", "Aro", "kfv", "Arm", "Arl", "Arv", "yDe", "Bpz", "yDd", "wCe", "wau", "wCd",
        "wat", "sEu", "shu", "sEt", "sht", "kdu", "kvu", "kdt", "kvt", "Anu", "Bru", "Ant", "Brt",
        "zDp", "Dpy", "Doz", "yDF", "ybh", "wCF", "wah", "wix", "sEh", "sgx", "sxx", "kcx", "ktx",
        "lvx", "Alx", "Bnx", "Drx", "bpw", "nuz", "boy", "boj", "Dmz", "bqz", "jps", "ruy", "jow",
        "ruj", "joi", "job", "bmy", "jqy", "bmj", "jqj", "jmw", "rtj", "jmi", "jmb", "blj", "jnj",
        "jli", "jlb", "jkr", "sCu", "sCt", "kFu", "kFt", "Afu", "Aft", "wDh", "sCh", "sax", "kEx",
        "khx", "Adx", "Avx", "Buz", "Duy", "Duj", "buw", "nxj", "bui", "bub", "Dtj", "bvj", "jus",
        "rxi", "jug", "rxb", "jua", "juD", "bti", "jvi", "btb", "jvb", "jtg", "rwr", "jta", "jtD",
        "bsr", "jtr", "jsq", "jsn", "Bxj", "Dxi", "Dxb", "bxg", "nyr", "bxa", "bxD", "Dwr", "bxr",
        "bwq", "bwn", "pjk", "urw", "ejA", "pbs", "uny", "ebk", "pDw", "ulz", "eDs", "pBy", "eBw",
        "zfc", "fjk", "prw", "zfE", "fbs", "pny", "zfC", "fDw", "plz", "zfB", "fBy", "yrc", "zfq",
        "frw", "yrE", "zfn", "fny", "yrC", "flz", "yrB", "xjc", "yrq", "xjE", "yrn", "xjC", "xjB",
        "uzc", "xjq", "uzE", "xjn", "uzC", "uzB", "pzc", "uzq", "pzE", "uzn", "pzC", "djA", "ors",
        "ufy", "dbk", "onw", "udz", "dDs", "oly", "dBw", "okz", "dAy", "zdo", "drs", "ovy", "zdm",
        "dnw", "otz", "zdl", "dly", "dkz", "yno", "zdv", "dvy", "ynm", "dtz", "ynl", "xbo", "ynv",
        "xbm", "xbl", "ujo", "xbv", "ujm", "ujl", "ozo", "ujv", "ozm", "ozl", "crk", "ofw", "uFz",
        "cns", "ody", "clw", "ocz", "cky", "ckj", "zcu", "cvw", "ohz", "zct", "cty", "csz", "ylu",
        "cxz", "ylt", "xDu", "xDt", "ubu", "ubt", "oju", "ojt", "cfs", "oFy", "cdw", "oEz", "ccy",
        "ccj", "zch", "chy", "cgz", "ykx", "xBx", "uDx", "cFw", "oCz", "cEy", "cEj", "caz", "cCy",
        "cCj", "FjA", "mrs", "tfy", "Fbk", "mnw", "tdz", "FDs", "mly", "FBw", "mkz", "FAy", "zFo",
        "Frs", "mvy", "zFm", "Fnw", "mtz", "zFl", "Fly", "Fkz", "yfo", "zFv", "Fvy", "yfm", "Ftz",
        "yfl", "wro", "yfv", "wrm", "wrl", "tjo", "wrv", "tjm", "tjl", "mzo", "tjv", "mzm", "mzl",
        "qrk", "vfw", "xpz", "hbA", "qns", "vdy", "hDk", "qlw", "vcz", "hBs", "qky", "hAw", "qkj",
        "hAi", "Erk", "mfw", "tFz", "hrk", "Ens", "mdy", "hns", "qty", "mcz", "hlw", "Eky", "hky",
        "Ekj", "hkj", "zEu", "Evw", "mhz", "zhu", "zEt", "hvw", "Ety", "zht", "hty", "Esz", "hsz",
        "ydu", "Exz", "yvu", "ydt", "hxz", "yvt", "wnu", "xru", "wnt", "xrt", "tbu", "vju", "tbt",
        "vjt", "mju", "mjt", "grA", "qfs", "vFy", "gnk", "qdw", "vEz", "gls", "qcy", "gkw", "qcj",
        "gki", "gkb", "Efs", "mFy", "gvs", "Edw", "mEz", "gtw", "qgz", "gsy", "Ecj", "gsj", "zEh",
        "Ehy", "zgx", "gxy", "Egz", "gwz", "ycx", "ytx", "wlx", "xnx", "tDx", "vbx", "mbx", "gfk",
        "qFw", "vCz", "gds", "qEy", "gcw", "qEj", "gci", "gcb", "EFw", "mCz", "ghw", "EEy", "ggy",
        "EEj", "ggj", "Eaz", "giz", "gFs", "qCy", "gEw", "qCj", "gEi", "gEb", "ECy", "gay", "ECj",
        "gaj", "gCw", "qBj", "gCi", "gCb", "EBj", "gDj", "gBi", "gBb", "Crk", "lfw", "spz", "Cns",
        "ldy", "Clw", "lcz", "Cky", "Ckj", "zCu", "Cvw", "lhz", "zCt", "Cty", "Csz", "yFu", "Cxz",
        "yFt", "wfu", "wft", "sru", "srt", "lju", "ljt", "arA", "nfs", "tpy", "ank", "ndw", "toz",
        "als", "ncy", "akw", "ncj", "aki", "akb", "Cfs", "lFy", "avs", "Cdw", "lEz", "atw", "ngz",
        "asy", "Ccj", "asj", "zCh", "Chy", "zax", "axy", "Cgz", "awz", "yEx", "yhx", "wdx", "wvx",
        "snx", "trx", "lbx", "rfk", "vpw", "xuz", "inA", "rds", "voy", "ilk", "rcw", "voj", "iks",
        "rci", "ikg", "rcb", "ika", "afk", "nFw", "tmz", "ivk", "ads", "nEy", "its", "rgy", "nEj",
        "isw", "aci", "isi", "acb", "isb", "CFw", "lCz", "ahw", "CEy", "ixw", "agy", "CEj", "iwy",
        "agj", "iwj", "Caz", "aiz", "iyz", "ifA", "rFs", "vmy", "idk", "rEw", "vmj", "ics", "rEi",
        "icg", "rEb", "ica", "icD", "aFs", "nCy", "ihs", "aEw", "nCj", "igw", "raj", "igi", "aEb",
        "igb", "CCy", "aay", "CCj", "iiy", "aaj", "iij", "iFk", "rCw", "vlj", "iEs", "rCi", "iEg",
        "rCb", "iEa", "iED", "aCw", "nBj", "iaw", "aCi", "iai", "aCb", "iab", "CBj", "aDj", "ibj",
        "iCs", "rBi", "iCg", "rBb", "iCa", "iCD", "aBi", "iDi", "aBb", "iDb", "iBg", "rAr", "iBa",
        "iBD", "aAr", "iBr", "iAq", "iAn", "Bfs", "kpy", "Bdw", "koz", "Bcy", "Bcj", "Bhy", "Bgz",
        "yCx", "wFx", "sfx", "krx", "Dfk", "lpw", "suz", "Dds", "loy", "Dcw", "loj", "Dci", "Dcb",
        "BFw", "kmz", "Dhw", "BEy", "Dgy", "BEj", "Dgj", "Baz", "Diz", "bfA", "nps", "tuy", "bdk",
        "now", "tuj", "bcs", "noi", "bcg", "nob", "bca", "bcD", "DFs", "lmy", "bhs", "DEw", "lmj",
        "bgw", "DEi", "bgi", "DEb", "bgb", "BCy", "Day", "BCj", "biy", "Daj", "bij", "rpk", "vuw",
        "xxj", "jdA", "ros", "vui", "jck", "rog", "vub", "jcc", "roa", "jcE", "roD", "jcC", "bFk",
        "nmw", "ttj", "jhk", "bEs", "nmi", "jgs", "rqi", "nmb", "jgg", "bEa", "jga", "bED", "jgD",
        "DCw", "llj", "baw", "DCi", "jiw", "bai", "DCb", "jii", "bab", "jib", "BBj", "DDj", "bbj",
        "jjj", "jFA", "rms", "vti", "jEk", "rmg", "vtb", "jEc", "rma", "jEE", "rmD", "jEC", "jEB",
        "bCs", "nli", "jas", "bCg", "nlb", "jag", "rnb", "jaa", "bCD", "jaD", "DBi", "bDi", "DBb",
        "jbi", "bDb", "jbb", "jCk", "rlg", "vsr", "jCc", "rla", "jCE", "rlD", "jCC", "jCB", "bBg",
        "nkr", "jDg", "bBa", "jDa", "bBD", "jDD", "DAr", "bBr", "jDr", "jBc", "rkq", "jBE", "rkn",
        "jBC", "jBB", "bAq", "jBq", "bAn", "jBn", "jAo", "rkf", "jAm", "jAl", "bAf", "jAv", "Apw",
        "kez", "Aoy", "Aoj", "Aqz", "Bps", "kuy", "Bow", "kuj", "Boi", "Bob", "Amy", "Bqy", "Amj",
        "Bqj", "Dpk", "luw", "sxj", "Dos", "lui", "Dog", "lub", "Doa", "DoD", "Bmw", "ktj", "Dqw",
        "Bmi", "Dqi", "Bmb", "Dqb", "Alj", "Bnj", "Drj", "bpA", "nus", "txi", "bok", "nug", "txb",
        "boc", "nua", "boE", "nuD", "boC", "boB", "Dms", "lti", "bqs", "Dmg", "ltb", "bqg", "nvb",
        "bqa", "DmD", "bqD", "Bli", "Dni", "Blb", "bri", "Dnb", "brb", "ruk", "vxg", "xyr", "ruc",
        "vxa", "ruE", "vxD", "ruC", "ruB", "bmk", "ntg", "twr", "jqk", "bmc", "nta", "jqc", "rva",
        "ntD", "jqE", "bmC", "jqC", "bmB", "jqB", "Dlg", "lsr", "bng", "Dla", "jrg", "bna", "DlD",
        "jra", "bnD", "jrD", "Bkr", "Dlr", "bnr", "jrr", "rtc", "vwq", "rtE", "vwn", "rtC", "rtB",
        "blc", "nsq", "jnc", "blE", "nsn", "jnE", "rtn", "jnC", "blB", "jnB", "Dkq", "blq", "Dkn",
        "jnq", "bln", "jnn", "rso", "vwf", "rsm", "rsl", "bko", "nsf", "jlo", "bkm", "jlm", "bkl",
        "jll", "Dkf", "bkv", "jlv", "rse", "rsd", "bke", "jku", "bkd", "jkt", "Aey", "Aej", "Auw",
        "khj", "Aui", "Aub", "Adj", "Avj", "Bus", "kxi", "Bug", "kxb", "Bua", "BuD", "Ati", "Bvi",
        "Atb", "Bvb", "Duk", "lxg", "syr", "Duc", "lxa", "DuE", "lxD", "DuC", "DuB", "Btg", "kwr",
        "Dvg", "lxr", "Dva", "BtD", "DvD", "Asr", "Btr", "Dvr", "nxc", "tyq", "nxE", "tyn", "nxC",
        "nxB", "Dtc", "lwq", "bvc", "nxq", "lwn", "bvE", "DtC", "bvC", "DtB", "bvB", "Bsq", "Dtq",
        "Bsn", "bvq", "Dtn", "bvn", "vyo", "xzf", "vym", "vyl", "nwo", "tyf", "rxo", "nwm", "rxm",
        "nwl", "rxl", "Dso", "lwf", "bto", "Dsm", "jvo", "btm", "Dsl", "jvm", "btl", "jvl", "Bsf",
        "Dsv", "btv", "jvv", "vye", "vyd", "nwe", "rwu", "nwd", "rwt", "Dse", "bsu", "Dsd", "jtu",
        "bst", "jtt", "vyF", "nwF", "rwh", "DsF", "bsh", "jsx", "Ahi", "Ahb", "Axg", "kir", "Axa",
        "AxD", "Agr", "Axr", "Bxc", "kyq", "BxE", "kyn", "BxC", "BxB", "Awq", "Bxq", "Awn", "Bxn",
        "lyo", "szf", "lym", "lyl", "Bwo", "kyf", "Dxo", "lyv", "Dxm", "Bwl", "Dxl", "Awf", "Bwv",
        "Dxv", "tze", "tzd", "lye", "nyu", "lyd", "nyt", "Bwe", "Dwu", "Bwd", "bxu", "Dwt", "bxt",
        "tzF", "lyF", "nyh", "BwF", "Dwh", "bwx", "Aiq", "Ain", "Ayo", "kjf", "Aym", "Ayl", "Aif",
        "Ayv", "kze", "kzd", "Aye", "Byu", "Ayd", "Byt", "szp"
    };

    private static final char[] BR_SET = {
        'A', 'B', 'C', 'D', 'E', 'F', '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[] PDF_TTF = {
        "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111",
        "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001",
        "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010",
        "11011", "11100", "11101", "11110", "11111", "01", "1111111101010100", "11111101000101001"
    };

    private static final int[] ASCII_X = {
        7, 8, 8, 4, 12, 4, 4, 8, 8, 8, 12, 4, 12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4,
        4, 4, 12, 8, 8, 4, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 8, 8, 8, 4, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 8, 8, 8, 8
    };

    private static final int[] ASCII_Y = {
        26, 10, 20, 15, 18, 21, 10, 28, 23, 24, 22, 20, 13, 16, 17, 19, 0, 1, 2, 3,
        4, 5, 6, 7, 8, 9, 14, 0, 1, 23, 2, 25, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 4, 5, 6, 24, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
        11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 21, 27, 9
    };

    private static final int[] MICRO_AUTOSIZE = {
        4, 6,  7, 8, 8,  10, 10, 12, 12, 13, 14, 16, 18, 18, 19, 20, 24, 24, 24, 29, 30, 33, 34, 37, 39, 46, 54, 58, 70, 72, 82, 90, 108, 126, // max codeword counts
        1, 14, 2, 7, 24, 3,  15, 25, 4,  8,  16, 5,  17, 26, 9,  6,  10, 18, 27, 11, 28, 12, 19, 13, 29, 20, 30, 21, 22, 31, 23, 32, 33,  34   // corresponding variant
    };

    /* Rows, columns, error codewords, k-offset of valid MicroPDF417 sizes from ISO/IEC 24728:2006 */
    private static final int[] MICRO_VARIANTS = {
        1,  1,  1,  1,  1,  1,  2, 2,  2,  2,  2,  2,  2,  3,  3,  3,  3,   3,   3,   3,   3,   3,   3,   4, 4,  4,  4,  4,   4,   4,   4,   4,   4,   4,  // columns
        11, 14, 17, 20, 24, 28, 8, 11, 14, 17, 20, 23, 26, 6,  8,  10, 12,  15,  20,  26,  32,  38,  44,  4, 6,  8,  10, 12,  15,  20,  26,  32,  38,  44, // rows
        7,  7,  7,  8,  8,  8,  8, 9,  9,  10, 11, 13, 15, 12, 14, 16, 18,  21,  26,  32,  38,  44,  50,  8, 12, 14, 16, 18,  21,  26,  32,  38,  44,  50, // k (EC codewords)
        0,  0,  0,  7,  7,  7,  7, 15, 15, 24, 34, 57, 84, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294, 7, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294 // offset
    };

    /* Following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24728:2006 tables 10, 11 and 12 */
    private static final int[] RAP_TABLE = {
        1, 8, 36, 19, 9,  25, 1, 1, 8, 36, 19, 9,  27, 1, 7, 15, 25, 37, 1,  1,  21, 15, 1,  47, 1, 7, 15, 25, 37, 1,  1,  21, 15, 1,  // left RAP
        0, 0, 0,  0,  0,  0,  0, 0, 0, 0,  0,  0,  0,  1, 7, 15, 25, 37, 17, 9,  29, 31, 25, 19, 1, 7, 15, 25, 37, 17, 9,  29, 31, 25, // centre RAP
        9, 8, 36, 19, 17, 33, 1, 9, 8, 36, 19, 17, 35, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, 43, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, // right RAP
        0, 3, 6,  0,  6,  0,  0, 0, 3, 6,  0,  6,  6,  0, 0, 6,  0,  0,  0,  0,  6,  6,  0,  3,  0, 0, 6,  0,  0,  0,  0,  6,  6,  0   // start cluster
    };

    /* Left and Right Row Address Pattern from Table 2 */
    private static final String[] RAPLR = {
        "",       "221311", "311311", "312211", "222211", "213211", "214111", "223111",
        "313111", "322111", "412111", "421111", "331111", "241111", "232111", "231211", "321211",
        "411211", "411121", "411112", "321112", "312112", "311212", "311221", "311131", "311122",
        "311113", "221113", "221122", "221131", "221221", "222121", "312121", "321121", "231121",
        "231112", "222112", "213112", "212212", "212221", "212131", "212122", "212113", "211213",
        "211123", "211132", "211141", "211231", "211222", "211312", "211321", "211411", "212311"
    };

    /* Centre Row Address Pattern from Table 2 */
    private static final String[] RAPC = {
        "",       "112231", "121231", "122131", "131131", "131221", "132121", "141121",
        "141211", "142111", "133111", "132211", "131311", "122311", "123211", "124111", "115111",
        "114211", "114121", "123121", "123112", "122212", "122221", "121321", "121411", "112411",
        "113311", "113221", "113212", "113122", "122122", "131122", "131113", "122113", "113113",
        "112213", "112222", "112312", "112321", "111421", "111331", "111322", "111232", "111223",
        "111133", "111124", "111214", "112114", "121114", "121123", "121132", "112132", "112141"
    };

    /* MicroPDF417 coefficients from ISO/IEC 24728:2006 Annex F */
    private static final int[] MICRO_COEFFS = {
        /* k = 7 */
        76, 925, 537, 597, 784, 691, 437,

        /* k = 8 */
        237, 308, 436, 284, 646, 653, 428, 379,

        /* k = 9 */
        567, 527, 622, 257, 289, 362, 501, 441, 205,

        /* k = 10 */
        377, 457, 64, 244, 826, 841, 818, 691, 266, 612,

        /* k = 11 */
        462, 45, 565, 708, 825, 213, 15, 68, 327, 602, 904,

        /* k = 12 */
        597, 864, 757, 201, 646, 684, 347, 127, 388, 7, 69, 851,

        /* k = 13 */
        764, 713, 342, 384, 606, 583, 322, 592, 678, 204, 184, 394, 692,

        /* k = 14 */
        669, 677, 154, 187, 241, 286, 274, 354, 478, 915, 691, 833, 105, 215,

        /* k = 15 */
        460, 829, 476, 109, 904, 664, 230, 5, 80, 74, 550, 575, 147, 868, 642,

        /* k = 16 */
        274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,

        /* k = 18 */
        279, 577, 315, 624, 37, 855, 275, 739, 120, 297, 312, 202, 560, 321, 233, 756,
        760, 573,

        /* k = 21 */
        108, 519, 781, 534, 129, 425, 681, 553, 422, 716, 763, 693, 624, 610, 310, 691,
        347, 165, 193, 259, 568,

        /* k = 26 */
        443, 284, 887, 544, 788, 93, 477, 760, 331, 608, 269, 121, 159, 830, 446, 893,
        699, 245, 441, 454, 325, 858, 131, 847, 764, 169,

        /* k = 32 */
        361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517,
        273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410,

        /* k = 38 */
        234, 228, 438, 848, 133, 703, 529, 721, 788, 322, 280, 159, 738, 586, 388, 684,
        445, 680, 245, 595, 614, 233, 812, 32, 284, 658, 745, 229, 95, 689, 920, 771,
        554, 289, 231, 125, 117, 518,

        /* k = 44 */
        476, 36, 659, 848, 678, 64, 764, 840, 157, 915, 470, 876, 109, 25, 632, 405,
        417, 436, 714, 60, 376, 97, 413, 706, 446, 21, 3, 773, 569, 267, 272, 213,
        31, 560, 231, 758, 103, 271, 572, 436, 339, 730, 82, 285,

        /* k = 50 */
        923, 797, 576, 875, 156, 706, 63, 81, 257, 874, 411, 416, 778, 50, 205, 303,
        188, 535, 909, 155, 637, 230, 534, 96, 575, 102, 264, 233, 919, 593, 865, 26,
        579, 623, 766, 146, 10, 739, 246, 127, 71, 244, 211, 477, 920, 876, 427, 820,
        718, 435
    };

    /**
     * Creates a new PDF417 symbol instance.
     */
    public Pdf417() {
        setBarHeight(3);
    }

    /**
     * Sets the default bar height (height of a single row) for this symbol (default value is <code>3</code>).
     *
     * @param barHeight the default bar height for this symbol
     */
    @Override
    public void setBarHeight(int barHeight) {
        super.setBarHeight(barHeight);
    }

    /**
     * Sets the width of the symbol by specifying the number of columns
     * of data codewords. Valid values are 1-30 for PDF417 and 1-4
     * for MicroPDF417.
     *
     * @param columns the number of data columns in the symbol
     */
    public void setDataColumns(int columns) {
        this.columns = columns;
    }

    /**
     * Returns the number of data columns used by this symbol, or {@code null}
     * if the number of data columns has not been set.
     *
     * @return the number of data columns used by this symbol
     */
    public Integer getDataColumns() {
        return columns;
    }

    /**
     * Sets the height of the symbol by specifying the number of rows
     * of data codewords. Valid values are 3-90 for PDF417 and 4-44
     * for MicroPDF417.
     *
     * @param rows the number of rows in the symbol
     */
    public void setRows(int rows) {
        this.rows = rows;
    }

    /**
     * Returns the number of rows used by this symbol, or {@code null} if
     * the number of rows has not been set.
     *
     * @return the number of rows used by this symbol
     */
    public Integer getRows() {
        return rows;
    }

    /**
     * Set the amount of the symbol which is dedicated to error correction
     * codewords. The number of codewords of error correction data is
     * determined by 2<sup>(eccLevel + 1)</sup>. This attribute is ignored
     * when using {@link Mode#MICRO micro} mode.
     *
     * @param eccLevel level of error correction (0-8)
     */
    public void setPreferredEccLevel(int eccLevel) {
        if (eccLevel < 0 || eccLevel > 8) {
            throw new IllegalArgumentException("ECC level must be between 0 and 8.");
        }
        preferredEccLevel = eccLevel;
    }

    /**
     * Returns the preferred error correction level.
     *
     * @return the preferred error correction level
     */
    public int getPreferredEccLevel() {
        return preferredEccLevel;
    }

    /**
     * Forces the use of the specified MicroPDF417 variant. Only valid
     * when using {@link Mode#MICRO micro} mode.
     *
     * @param variant the MicroPDF417 variant to use
     */
    public void setVariant(int variant) {
        if (symbolMode != Mode.MICRO) {
            throw new IllegalArgumentException("Can only set variant when using MICRO mode.");
        }
        if (variant < 1 || variant > 34) {
            throw new IllegalArgumentException("Variant must be between 1 and 34.");
        }
        this.columns = MICRO_VARIANTS[variant - 1];
        this.rows = MICRO_VARIANTS[variant - 1 + 34];
    }

    /**
     * If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format
     * (Macro PDF417), this method sets the position of this symbol in the series. Valid values are
     * 1 through 99,999 inclusive.
     *
     * @param position the position of this PDF417 symbol in the structured append series
     */
    public void setStructuredAppendPosition(int position) {
        if (position < 1 || position > 99_999) {
            throw new IllegalArgumentException("Invalid PDF417 structured append position: " + position);
        }
        this.structuredAppendPosition = position;
    }

    /**
     * Returns the position of this PDF417 symbol in a series of symbols using structured append
     * (Macro PDF417). If this symbol is not part of such a series, this method will return <code>1</code>.
     *
     * @return the position of this PDF417 symbol in a series of symbols using structured append
     */
    public int getStructuredAppendPosition() {
        return structuredAppendPosition;
    }

    /**
     * If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format
     * (Macro PDF417), this method sets the total number of symbols in the series. Valid values are
     * 1 through 99,999 inclusive. A value of 1 indicates that this symbol is not part of a structured
     * append series.
     *
     * @param total the total number of PDF417 symbols in the structured append series
     */
    public void setStructuredAppendTotal(int total) {
        if (total < 1 || total > 99_999) {
            throw new IllegalArgumentException("Invalid PDF417 structured append total: " + total);
        }
        this.structuredAppendTotal = total;
    }

    /**
     * Returns the size of the series of PDF417 symbols using structured append (Macro PDF417) that
     * this symbol is part of. If this symbol is not part of a structured append series, this method
     * will return <code>1</code>.
     *
     * @return size of the series that this symbol is part of
     */
    public int getStructuredAppendTotal() {
        return structuredAppendTotal;
    }

    /**
     * If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format
     * (Macro PDF417), this method sets the unique file ID for the series. Valid values are 0 through
     * 899 inclusive.
     *
     * @param fileId the unique file ID for the series that this symbol is part of
     */
    public void setStructuredAppendFileId(int fileId) {
        if (fileId < 0 || fileId > 899) {
            throw new IllegalArgumentException("Invalid PDF417 structured append file ID: " + fileId);
        }
        this.structuredAppendFileId = fileId;
    }

    /**
     * Returns the unique file ID of the series of PDF417 symbols using structured append (Macro PDF417)
     * that this symbol is part of. If this symbol is not part of a structured append series, this method
     * will return <code>0</code>.
     *
     * @return the unique file ID for the series that this symbol is part of
     */
    public int getStructuredAppendFileId() {
        return structuredAppendFileId;
    }

    public void setMode(Mode mode) {
        symbolMode = mode;
    }

    public Mode getMode() {
        return symbolMode;
    }

    @Override
    protected void encode() {

        eciProcess();

        switch (symbolMode) {
            case MICRO:
                processMicroPdf417();
                break;
            case NORMAL:
            case TRUNCATED:
            default:
                processPdf417();
                break;
        }
    }

    private void processPdf417() {
        int j, loop, offset;
        int[] mccorrection = new int[520];
        int total;
        int c1, c2, c3;
        int[] dummy = new int[35];
        int selectedECCLevel;
        StringBuilder codebarre = new StringBuilder();
        StringBuilder bin = new StringBuilder();

        List< Block > blocks = createBlocks(inputData);

        /* now compress the data */
        codeWordCount = 0;

        if (readerInit) {
            codeWords[codeWordCount] = 921; /* Reader Initialisation */
            codeWordCount++;
        }

        if (eciMode != 3) {
            /* Encoding ECI assignment number, from ISO/IEC 15438 Table 8 */
            if (eciMode <= 899) {
                codeWords[codeWordCount] = 927;
                codeWordCount++;
                codeWords[codeWordCount] = eciMode;
                codeWordCount++;
            }

            if ((eciMode >= 900) && (eciMode <= 810899)) {
                codeWords[codeWordCount] = 926;
                codeWordCount++;
                codeWords[codeWordCount] = (eciMode / 900) - 1;
                codeWordCount++;
                codeWords[codeWordCount] = eciMode % 900;
                codeWordCount++;
            }

            if ((eciMode >= 810900) && (eciMode <= 811799)) {
                codeWords[codeWordCount] = 925;
                codeWordCount++;
                codeWords[codeWordCount] = eciMode - 810900;
                codeWordCount++;
            }
        }

        int blockCount = 0;
        for (int i = 0; i < blocks.size(); i++) {
            Block block = blocks.get(i);
            switch (block.mode) {
                case TEX:
                    /* text mode */
                    boolean firstBlock = (i == 0);
                    processText(blockCount, block.length, firstBlock);
                    break;
                case BYT:
                    /* octet stream mode */
                    EncodingMode lastMode = (i == 0 ? EncodingMode.TEX : blocks.get(i - 1).mode);
                    processBytes(blockCount, block.length, lastMode);
                    break;
                case NUM:
                    /* numeric mode */
                    processNumbers(inputData, blockCount, block.length, false);
                    break;
                default:
                    throw new OkapiException("Unknown block type: " + block.mode);
            }
            blockCount += block.length;
        }

        addMacroCodewords();

        info("Codewords: ");
        for (int i = 0; i < codeWordCount; i++) {
            infoSpace(codeWords[i]);
        }
        infoLine();

        /* Now take care of the number of CWs per row */

        // if we have to default the ECC level, do so per the
        // recommendations in the specification (Table E.1)
        selectedECCLevel = preferredEccLevel;
        if (selectedECCLevel < 0) {
            if (codeWordCount <= 40) {
                selectedECCLevel = 2;
            } else if (codeWordCount <= 160) {
                selectedECCLevel = 3;
            } else if (codeWordCount <= 320) {
                selectedECCLevel = 4;
            } else if (codeWordCount <= 863) {
                selectedECCLevel = 5;
            } else {
                selectedECCLevel = 6;
            }
        }

        int k = 1 << (selectedECCLevel + 1); // error correction codeword count
        int dataCodeWordCount = codeWordCount + k + 1; // not including padding

        validateRows(3, 90);
        validateColumns(1, 30);

        if (columns != null) {
            if (rows != null) {
                // user specified both columns and rows; make sure the data fits
                if (columns * rows < dataCodeWordCount) {
                    throw new OkapiException("Too few rows (" + rows + ") and columns (" + columns + ") to hold codewords (" + dataCodeWordCount + ")");
                }
            } else {
                // user only specified column count; figure out row count
                rows = (int) Math.ceil(dataCodeWordCount / (double) columns);
            }
        } else {
            if (rows != null) {
                // user only specified row count; figure out column count
                columns = (int) Math.ceil(dataCodeWordCount / (double) rows);
            } else {
                // user didn't specify columns or rows; figure both out
                columns = (int) (0.5 + Math.sqrt((dataCodeWordCount - 1) / 3.0));
                rows = (int) Math.ceil(dataCodeWordCount / (double) columns);
            }
        }

        validateRows(3, 90);
        validateColumns(1, 30);

        /* add the padding */
        int paddingCount = (columns * rows) - codeWordCount - k - 1;
        while (paddingCount > 0) {
            codeWords[codeWordCount] = 900;
            codeWordCount++;
            paddingCount--;
        }

        /* add the length descriptor */
        for (int i = codeWordCount; i > 0; i--) {
            codeWords[i] = codeWords[i - 1];
        }
        codeWordCount++;
        codeWords[0] = codeWordCount;

        /* 796 - we now take care of the Reed Solomon codes */
        switch (selectedECCLevel) {
            case 1:
                offset = 2;
                break;
            case 2:
                offset = 6;
                break;
            case 3:
                offset = 14;
                break;
            case 4:
                offset = 30;
                break;
            case 5:
                offset = 62;
                break;
            case 6:
                offset = 126;
                break;
            case 7:
                offset = 254;
                break;
            case 8:
                offset = 510;
                break;
            default:
                offset = 0;
                break;
        }

        for (loop = 0; loop < 520; loop++) {
            mccorrection[loop] = 0;
        }

        for (int i = 0; i < codeWordCount; i++) {
            total = (codeWords[i] + mccorrection[k - 1]) % 929;
            for (j = k - 1; j > 0; j--) {
                mccorrection[j] = (mccorrection[j - 1] + 929 - (total * COEFRS[offset + j]) % 929) % 929;
            }
            mccorrection[0] = (929 - (total * COEFRS[offset + j]) % 929) % 929;
        }

        infoLine("Data Codewords: " + codeWordCount);
        infoLine("ECC Codewords: " + k);

        /* we add these codes to the string */
        for (int i = k - 1; i >= 0; i--) {
            codeWords[codeWordCount++] = mccorrection[i] != 0 ? 929 - mccorrection[i] : 0;
        }

        /* make sure total codeword count isn't too high */
        if (codeWordCount > 929) {
            throw new OkapiException("Too many codewords required (" + codeWordCount + ", but max is 929)");
        }

        /* 818 - The CW string is finished */
        c1 = (rows - 1) / 3;
        c2 = (selectedECCLevel * 3) + (rows - 1) % 3;
        c3 = columns - 1;

        readable = "";
        row_count = rows;
        pattern = new String[rows];
        row_height = new int[rows];
        infoLine("Grid Size: " + columns + " X " + rows);

        /* we now encode each row */
        for (int i = 0; i < rows; i++) {
            for (j = 0; j < columns; j++) {
                dummy[j + 1] = codeWords[i * columns + j];
            }
            k = (i / 3) * 30;
            switch (i % 3) {
                case 0:
                    offset = 0; // cluster 0
                    dummy[0] = k + c1; // left row indicator
                    dummy[columns + 1] = k + c3; // right row indicator
                    break;
                case 1:
                    offset = 929; // cluster 3
                    dummy[0] = k + c2; // left row indicator
                    dummy[columns + 1] = k + c1; // right row indicator
                    break;
                case 2:
                    offset = 1858; // cluster 6
                    dummy[0] = k + c3; // left row indicator
                    dummy[columns + 1] = k + c2; // right row indicator
                    break;
            }
            codebarre.setLength(0);
            codebarre.append("+*");
            for (j = 0; j <= columns + 1; j++) {
                if (!(symbolMode == Mode.TRUNCATED && j > columns)) {
                    codebarre.append(CODAGEMC[offset + dummy[j]]);
                    codebarre.append('*');
                }
            }
            if(symbolMode != Mode.TRUNCATED) {
                codebarre.append('-');
            }
            bin.setLength(0);
            for (j = 0; j < codebarre.length(); j++) {
                bin.append(PDF_TTF[positionOf(codebarre.charAt(j), BR_SET)]);
            }
            pattern[i] = bin2pat(bin);
            row_height[i] = default_height;
        }
    }

    private void processMicroPdf417() { /* like PDF417 only much smaller! */

        int k, j, longueur, offset;
        int total;
        int LeftRAPStart, CentreRAPStart, RightRAPStart, StartCluster;
        int LeftRAP, CentreRAP, RightRAP, Cluster, flip, loop;
        int[] dummy = new int[5];
        int[] mccorrection = new int[50];
        StringBuilder codebarre = new StringBuilder();
        StringBuilder bin = new StringBuilder();

        /* Encoding starts out the same as PDF417, so use the same code */

        List< Block > blocks = createBlocks(inputData);

        /* 541 - now compress the data */
        codeWordCount = 0;
        if (readerInit) {
            codeWords[codeWordCount] = 921; /* Reader Initialisation */
            codeWordCount++;
        }

        if (eciMode != 3) {
            /* Encoding ECI assignment number, from ISO/IEC 15438 Table 8 */
            if (eciMode <= 899) {
                codeWords[codeWordCount] = 927;
                codeWordCount++;
                codeWords[codeWordCount] = eciMode;
                codeWordCount++;
            }

            if ((eciMode >= 900) && (eciMode <= 810899)) {
                codeWords[codeWordCount] = 926;
                codeWordCount++;
                codeWords[codeWordCount] = (eciMode / 900) - 1;
                codeWordCount++;
                codeWords[codeWordCount] = eciMode % 900;
                codeWordCount++;
            }

            if ((eciMode >= 810900) && (eciMode <= 811799)) {
                codeWords[codeWordCount] = 925;
                codeWordCount++;
                codeWords[codeWordCount] = eciMode - 810900;
                codeWordCount++;
            }
        }

        int blockCount = 0;
        for (int i = 0; i < blocks.size(); i++) {
            Block block = blocks.get(i);
            switch (block.mode) {
                case TEX:
                    /* text mode */
                    processText(blockCount, block.length, false); // TODO: this shouldn't always be false?
                    break;
                case BYT:
                    /* octet stream mode */
                    EncodingMode lastMode = (i == 0 ? EncodingMode.TEX : blocks.get(i - 1).mode);
                    processBytes(blockCount, block.length, lastMode);
                    break;
                case NUM:
                    /* numeric mode */
                    processNumbers(inputData, blockCount, block.length, false);
                    break;
                default:
                    throw new OkapiException("Unknown block type: " + block.mode);
            }
            blockCount += block.length;
        }

        addMacroCodewords();

        info("Codewords: ");
        for (int i = 0; i < codeWordCount; i++) {
            infoSpace(codeWords[i]);
        }
        infoLine();

        /* This is where it all changes! */

        validateRows(4, 44);
        validateColumns(1, 4);

        if (columns != null) {
            int max;
            switch (columns) {
                case 1:
                    max = 20;
                    break;
                case 2:
                    max = 37;
                    break;
                case 3:
                    max = 82;
                    break;
                case 4:
                    max = 126;
                    break;
                default:
                    throw new OkapiException("Invalid column count: " + columns);
            }
            if (codeWordCount > max) {
                throw new OkapiException("Too few columns (" + columns + ") to hold data codewords (" + codeWordCount + ")");
            }
        }

        /* Now figure out which variant of the symbol to use and load values accordingly */

        int variant = getMicroPdf417Variant(codeWordCount, columns, rows);

        /* Now we have the variant we can load the data */

        variant--;
        columns = MICRO_VARIANTS[variant]; /* columns */
        rows = MICRO_VARIANTS[variant + 34]; /* rows */
        k = MICRO_VARIANTS[variant + 68]; /* number of EC CWs */
        longueur = (columns * rows) - k; /* number of non-EC CWs */
        int padding = longueur - codeWordCount; /* amount of padding required */
        offset = MICRO_VARIANTS[variant + 102]; /* coefficient offset */

        infoLine("Data Codewords: " + longueur);
        infoLine("ECC Codewords: " + k);

        /* We add the padding */
        while (padding > 0) {
            codeWords[codeWordCount] = 900;
            codeWordCount++;
            padding--;
        }

        /* Reed-Solomon error correction */
        longueur = codeWordCount;
        for (loop = 0; loop < 50; loop++) {
            mccorrection[loop] = 0;
        }

        for (int i = 0; i < longueur; i++) {
            total = (codeWords[i] + mccorrection[k - 1]) % 929;
            for (j = k - 1; j >= 0; j--) {
                if (j == 0) {
                    mccorrection[j] = (929 - (total * MICRO_COEFFS[offset + j]) % 929) % 929;
                } else {
                    mccorrection[j] = (mccorrection[j - 1] + 929 - (total * MICRO_COEFFS[offset + j]) % 929) % 929;
                }
            }
        }

        for (j = 0; j < k; j++) {
            if (mccorrection[j] != 0) {
                mccorrection[j] = 929 - mccorrection[j];
            }
        }
        /* we add these codes to the string */
        for (int i = k - 1; i >= 0; i--) {
            codeWords[codeWordCount] = mccorrection[i];
            codeWordCount++;
        }

        /* Now get the RAP (Row Address Pattern) start values */
        LeftRAPStart = RAP_TABLE[variant];
        CentreRAPStart = RAP_TABLE[variant + 34];
        RightRAPStart = RAP_TABLE[variant + 68];
        StartCluster = RAP_TABLE[variant + 102] / 3;

        /* That's all values loaded, get on with the encoding */

        LeftRAP = LeftRAPStart;
        CentreRAP = CentreRAPStart;
        RightRAP = RightRAPStart;
        Cluster = StartCluster; /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */

        readable = "";
        pattern = new String[rows];
        row_count = rows;
        row_height = new int[rows];

        infoLine("Grid Size: " + columns + " X " + row_count);

        for (int i = 0; i < rows; i++) {
            codebarre.setLength(0);
            offset = 929 * Cluster;
            for (j = 0; j < 5; j++) {
                dummy[j] = 0;
            }
            for (j = 0; j < columns; j++) {
                dummy[j + 1] = codeWords[i * columns + j];
            }

            /* Copy the data into codebarre */
            codebarre.append(RAPLR[LeftRAP]);
            codebarre.append('1');
            codebarre.append(CODAGEMC[offset + dummy[1]]);
            codebarre.append('1');
            if (columns == 3) {
                codebarre.append(RAPC[CentreRAP]);
            }
            if (columns >= 2) {
                codebarre.append('1');
                codebarre.append(CODAGEMC[offset + dummy[2]]);
                codebarre.append('1');
            }
            if (columns == 4) {
                codebarre.append(RAPC[CentreRAP]);
            }
            if (columns >= 3) {
                codebarre.append('1');
                codebarre.append(CODAGEMC[offset + dummy[3]]);
                codebarre.append('1');
            }
            if (columns == 4) {
                codebarre.append('1');
                codebarre.append(CODAGEMC[offset + dummy[4]]);
                codebarre.append('1');
            }
            codebarre.append(RAPLR[RightRAP]);
            codebarre.append('1'); /* stop */

            /* Now codebarre is a mixture of letters and numbers */

            flip = 1;
            bin.setLength(0);
            for (loop = 0; loop < codebarre.length(); loop++) {
                if ((codebarre.charAt(loop) >= '0') && (codebarre.charAt(loop) <= '9')) {
                    for (k = 0; k < Character.getNumericValue(codebarre.charAt(loop)); k++) {
                        if (flip == 0) {
                            bin.append('0');
                        } else {
                            bin.append('1');
                        }
                    }
                    if (flip == 0) {
                        flip = 1;
                    } else {
                        flip = 0;
                    }
                } else {
                    bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]);
                }
            }

            /* so now pattern[] holds the string of '1's and '0's. - copy this to the symbol */
            pattern[i] = bin2pat(bin);
            row_height[i] = default_height;

            /* Set up RAPs and Cluster for next row */
            LeftRAP++;
            CentreRAP++;
            RightRAP++;
            Cluster++;

            if (LeftRAP == 53) {
                LeftRAP = 1;
            }
            if (CentreRAP == 53) {
                CentreRAP = 1;
            }
            if (RightRAP == 53) {
                RightRAP = 1;
            }
            if (Cluster == 3) {
                Cluster = 0;
            }
        }
    }

    private void validateRows(int min, int max) {
        if (rows != null) {
            if (rows < min) {
                throw new OkapiException("Too few rows (" + rows + ")");
            } else if (rows > max) {
                throw new OkapiException("Too many rows (" + rows + ")");
            }
        }
    }

    private void validateColumns(int min, int max) {
        if (columns != null) {
            if (columns < min) {
                throw new OkapiException("Too few columns (" + columns + ")");
            } else if (columns > max) {
                throw new OkapiException("Too many columns (" + columns + ")");
            }
        }
    }

    private static EncodingMode chooseMode(int codeascii) {
        if (codeascii >= '0' && codeascii <= '9') {
            return EncodingMode.NUM;
        } else if (codeascii == '\t' || codeascii == '\n' || codeascii == '\r' || (codeascii >= ' ' && codeascii <= '~')) {
            return EncodingMode.TEX;
        } else {
            return EncodingMode.BYT;
        }
    }

    private static int getMicroPdf417Variant(int codeWordCount, Integer columns, Integer rows) {
        for (int i = 0; i < 34; i++) {
            int maxCodewordCount = MICRO_AUTOSIZE[i];
            if (codeWordCount <= maxCodewordCount) {
                int variant = MICRO_AUTOSIZE[i + 34];
                int columnsForThisVariant = MICRO_VARIANTS[variant - 1];
                int rowsForThisVariant = MICRO_VARIANTS[variant - 1 + 34];
                if ((columns == null || columns == columnsForThisVariant) && (rows == null || rows == rowsForThisVariant)) {
                    return variant;
                }
            }
        }
        throw new OkapiException("Unable to determine MicroPDF417 variant for " + codeWordCount + " codewords");
    }

    /** Determines the encoding block groups for the specified data. */
    private static List< Block > createBlocks(int[] data) {

        List< Block > blocks = new ArrayList<>();
        Block current = null;

        for (int i = 0; i < data.length; i++) {
            EncodingMode mode = chooseMode(data[i]);
            if ((current != null && current.mode == mode) &&
                (mode != EncodingMode.NUM || current.length < MAX_NUMERIC_COMPACTION_BLOCK_SIZE)) {
                current.length++;
            } else {
                current = new Block(mode);
                blocks.add(current);
            }
        }

        smoothBlocks(blocks);

        return blocks;
    }

    /** Combines adjacent blocks of different types in very specific scenarios. */
    private static void smoothBlocks(List< Block > blocks) {

        for (int i = 0; i < blocks.size(); i++) {
            Block block = blocks.get(i);
            EncodingMode last = (i > 0 ? blocks.get(i - 1).mode : EncodingMode.FALSE);
            EncodingMode next = (i < blocks.size() - 1 ? blocks.get(i + 1).mode : EncodingMode.FALSE);
            if (block.mode == EncodingMode.NUM) {
                if (i == 0) { /* first block */
                    if (next == EncodingMode.TEX && block.length < 8) {
                        block.mode = EncodingMode.TEX;
                    } else if (next == EncodingMode.BYT && block.length == 1) {
                        block.mode = EncodingMode.BYT;
                    }
                } else if (i == blocks.size() - 1) { /* last block */
                    if (last == EncodingMode.TEX && block.length < 7) {
                        block.mode = EncodingMode.TEX;
                    } else if (last == EncodingMode.BYT && block.length == 1) {
                        block.mode = EncodingMode.BYT;
                    }
                } else { /* not first or last block */
                    if (last == EncodingMode.BYT && next == EncodingMode.BYT && block.length < 4) {
                        block.mode = EncodingMode.BYT;
                    } else if (last == EncodingMode.BYT && next == EncodingMode.TEX && block.length < 4) {
                        block.mode = EncodingMode.TEX;
                    } else if (last == EncodingMode.TEX && next == EncodingMode.BYT && block.length < 5) {
                        block.mode = EncodingMode.TEX;
                    } else if (last == EncodingMode.TEX && next == EncodingMode.TEX && block.length < 8) {
                        block.mode = EncodingMode.TEX;
                    } else if (last == EncodingMode.NUM && next == EncodingMode.TEX && block.length < 8) {
                        block.mode = EncodingMode.TEX;
                    }
                }
            }
        }

        mergeBlocks(blocks);

        for (int i = 0; i < blocks.size(); i++) {
            Block block = blocks.get(i);
            EncodingMode last = (i > 0 ? blocks.get(i - 1).mode : EncodingMode.FALSE);
            EncodingMode next = (i < blocks.size() - 1 ? blocks.get(i + 1).mode : EncodingMode.FALSE);
            if (block.mode == EncodingMode.TEX && i > 0) { /* not the first */
                if (i == blocks.size() - 1) { /* the last one */
                    if (last == EncodingMode.BYT && block.length == 1) {
                        block.mode = EncodingMode.BYT;
                    }
                } else { /* not the last one */
                    if (last == EncodingMode.BYT && next == EncodingMode.BYT && block.length < 5) {
                        block.mode = EncodingMode.BYT;
                    }
                    if (((last == EncodingMode.BYT && next != EncodingMode.BYT) ||
                         (last != EncodingMode.BYT && next == EncodingMode.BYT)) && (block.length < 3)) {
                        block.mode = EncodingMode.BYT;
                    }
                }
            }
        }

        mergeBlocks(blocks);
    }

    /** Combines adjacent blocks of the same type. */
    private static void mergeBlocks(List< Block > blocks) {
        for (int i = 1; i < blocks.size(); i++) {
            Block b1 = blocks.get(i - 1);
            Block b2 = blocks.get(i);
            if ((b1.mode == b2.mode) &&
                (b1.mode != EncodingMode.NUM || b1.length + b2.length <= MAX_NUMERIC_COMPACTION_BLOCK_SIZE)) {
                b1.length += b2.length;
                blocks.remove(i);
                i--;
            }
        }
    }

    private void processText(int start, int length, boolean skipLatch) {
        int j, blockIndext, curtable;
        int codeascii;
        int wnet = 0;
        int[] listet0 = new int[length];
        int[] listet1 = new int[length];
        int[] chainet = new int[length * 4];

        /* listet will contain the table numbers and the value of each characters */
        for (blockIndext = 0; blockIndext < length; blockIndext++) {
            codeascii = inputData[start + blockIndext];
            switch (codeascii) {
            case '\t':
                listet0[blockIndext] = 12;
                listet1[blockIndext] = 12;
                break;
            case '\n':
                listet0[blockIndext] = 8;
                listet1[blockIndext] = 15;
                break;
            case 13:
                listet0[blockIndext] = 12;
                listet1[blockIndext] = 11;
                break;
            default:
                listet0[blockIndext] = ASCII_X[codeascii - 32];
                listet1[blockIndext] = ASCII_Y[codeascii - 32];
                break;
            }
        }

        curtable = 1; /* default table */
        for (j = 0; j < length; j++) {
            if ((listet0[j] & curtable) != 0) { /* The character is in the current table */
                chainet[wnet] = listet1[j];
                wnet++;
            } else { /* Obliged to change table */
                boolean flag = false; /* True if we change table for only one character */
                if (j == (length - 1)) {
                    flag = true;
                } else {
                    if ((listet0[j] & listet0[j + 1]) == 0) {
                        flag = true;
                    }
                }

                if (flag) { /* we change only one character - look for temporary switch */
                    if (((listet0[j] & 1) != 0) && (curtable == 2)) { /* T_UPP */
                        chainet[wnet] = 27;
                        chainet[wnet + 1] = listet1[j];
                        wnet += 2;
                    }
                    if ((listet0[j] & 8) != 0) { /* T_PUN */
                        chainet[wnet] = 29;
                        chainet[wnet + 1] = listet1[j];
                        wnet += 2;
                    }
                    if (!((((listet0[j] & 1) != 0) && (curtable == 2)) || ((listet0[j] & 8) != 0))) {
                        /* No temporary switch available */
                        flag = false;
                    }
                }

                if (!(flag)) {
                    int newtable;

                    if (j == (length - 1)) {
                        newtable = listet0[j];
                    } else {
                        if ((listet0[j] & listet0[j + 1]) == 0) {
                            newtable = listet0[j];
                        } else {
                            newtable = listet0[j] & listet0[j + 1];
                        }
                    }

                    /* Maintain the first if several tables are possible */
                    switch (newtable) {
                    case 3:
                    case 5:
                    case 7:
                    case 9:
                    case 11:
                    case 13:
                    case 15:
                        newtable = 1;
                        break;
                    case 6:
                    case 10:
                    case 14:
                        newtable = 2;
                        break;
                    case 12:
                        newtable = 4;
                        break;
                    }

                    /* select the switch */
                    switch (curtable) {
                    case 1:
                        switch (newtable) {
                        case 2:
                            chainet[wnet] = 27;
                            wnet++;
                            break;
                        case 4:
                            chainet[wnet] = 28;
                            wnet++;
                            break;
                        case 8:
                            chainet[wnet] = 28;
                            wnet++;
                            chainet[wnet] = 25;
                            wnet++;
                            break;
                        }
                        break;
                    case 2:
                        switch (newtable) {
                        case 1:
                            chainet[wnet] = 28;
                            wnet++;
                            chainet[wnet] = 28;
                            wnet++;
                            break;
                        case 4:
                            chainet[wnet] = 28;
                            wnet++;
                            break;
                        case 8:
                            chainet[wnet] = 28;
                            wnet++;
                            chainet[wnet] = 25;
                            wnet++;
                            break;
                        }
                        break;
                    case 4:
                        switch (newtable) {
                        case 1:
                            chainet[wnet] = 28;
                            wnet++;
                            break;
                        case 2:
                            chainet[wnet] = 27;
                            wnet++;
                            break;
                        case 8:
                            chainet[wnet] = 25;
                            wnet++;
                            break;
                        }
                        break;
                    case 8:
                        switch (newtable) {
                        case 1:
                            chainet[wnet] = 29;
                            wnet++;
                            break;
                        case 2:
                            chainet[wnet] = 29;
                            wnet++;
                            chainet[wnet] = 27;
                            wnet++;
                            break;
                        case 4:
                            chainet[wnet] = 29;
                            wnet++;
                            chainet[wnet] = 28;
                            wnet++;
                            break;
                        }
                        break;
                    }
                    curtable = newtable;
                    /* at last we add the character */
                    chainet[wnet] = listet1[j];
                    wnet++;
                }
            }
        }

        if ((wnet & 1) != 0) {
            chainet[wnet] = 29;
            wnet++;
        }

        /* Now translate the string chainet into codewords */

        if (!skipLatch) {
            // text compaction mode is the default mode for PDF417,
            // so no need for an explicit latch if this is the first block
            codeWords[codeWordCount] = 900;
            codeWordCount++;
        }

        for (j = 0; j < wnet; j += 2) {
            int cw_number = (30 * chainet[j]) + chainet[j + 1];
            codeWords[codeWordCount] = cw_number;
            codeWordCount++;
        }
    }

    private void processBytes(int start, int length, EncodingMode lastMode) {
        int len = 0;
        int chunkLen = 0;
        BigInteger mantisa;
        BigInteger total;
        BigInteger word;

        mantisa = new BigInteger("0");
        total = new BigInteger("0");

        if (length == 1 && lastMode == EncodingMode.TEX) {
            codeWords[codeWordCount++] = 913;
            codeWords[codeWordCount++] = inputData[start];
        } else {
            /* select the switch for multiple of 6 bytes */
            if (length % 6 == 0) {
                codeWords[codeWordCount++] = 924;
            } else {
                codeWords[codeWordCount++] = 901;
            }

            while (len < length) {
                chunkLen = length - len;
                if (6 <= chunkLen) /* Take groups of 6 */{
                    chunkLen = 6;
                    len += chunkLen;
                    total = BigInteger.valueOf(0);

                    while ((chunkLen--) != 0) {
                        mantisa = BigInteger.valueOf(inputData[start++]);
                        total = total.or(mantisa.shiftLeft(chunkLen * 8));
                    }

                    chunkLen = 5;

                    while ((chunkLen--) != 0) {

                        word = total.mod(BigInteger.valueOf(900));
                        codeWords[codeWordCount + chunkLen] = word.intValue();
                        total = total.divide(BigInteger.valueOf(900));
                    }
                    codeWordCount += 5;
                } else /* If it remain a group of less than 6 bytes */{
                    len += chunkLen;
                    while ((chunkLen--) != 0) {
                        codeWords[codeWordCount++] = inputData[start++];
                    }
                }
            }
        }
    }

    private void processNumbers(int[] data, int start, int length, boolean skipLatch) {

        BigInteger tVal, dVal;
        int[] d = new int[16];
        int cw_count;

        if (!skipLatch) {
            // we don't need to latch to numeric mode in some cases, e.g.
            // during numeric compaction of the Macro PDF417 segment index
            codeWords[codeWordCount++] = 902;
        }

        StringBuilder t = new StringBuilder(length + 1);
        t.append('1');
        for (int i = 0; i < length; i++) {
            t.append((char) data[start + i]);
        }

        tVal = new BigInteger(t.toString());

        cw_count = 0;
        do {
            dVal = tVal.mod(BigInteger.valueOf(900));
            d[cw_count] = dVal.intValue();
            tVal = tVal.divide(BigInteger.valueOf(900));
            cw_count++;
        } while (tVal.compareTo(BigInteger.ZERO) == 1);

        for (int i = cw_count - 1; i >= 0; i--) {
            codeWords[codeWordCount++] = d[i];
        }
    }

    /** Adds the Macro PDF417 control block codewords (if any). */
    private void addMacroCodewords() {

        // if the structured append series size is 1, this isn't
        // actually part of a structured append series
        if (structuredAppendTotal == 1) {
            return;
        }

        // add the Macro marker codeword
        codeWords[codeWordCount++] = 928;

        // add the segment index, padded with leading zeros to five digits
        // use numeric compaction, but no latch
        int segmentIndex = structuredAppendPosition - 1;
        int[] data = new int[5];
        for (int x = data.length - 1; x >= 0; x--) {
            data[x] = '0' + (segmentIndex % 10);
            segmentIndex /= 10;
        }
        processNumbers(data, 0, data.length, true);

        // add the file ID (base 900, which is easy since we limit
        // file ID values to the range 0 to 899)
        codeWords[codeWordCount++] = structuredAppendFileId;

        // NOTE: we could add the optional segment count field here, but
        // it doesn't appear to be necessary... if we do eventually decide
        // to add it, it will probably be [923, 001, count1, count2]

        // add the terminator to the last symbol of the series
        boolean last = (structuredAppendPosition == structuredAppendTotal);
        if (last) {
            codeWords[codeWordCount++] = 922;
        }
    }

    private static class Block {

        public EncodingMode mode;
        public int length;

        public Block(EncodingMode mode) {
            this.mode = mode;
            this.length = 1;
        }

        @Override
        public String toString() {
            return mode + "x" + length;
        }
    }
}
 public class Pharmacode extends Symbol {

    @Override
    protected void encode() {
        int tester = 0;
        int i;

        String inter = "";
        String dest = "";

        if (content.length() > 6) {
            throw new OkapiException("Input too long");
        }

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

        for (i = 0; i < content.length(); i++) {
            tester *= 10;
            tester += Character.getNumericValue(content.charAt(i));
        }

        if (tester < 3 || tester > 131070) {
            throw new OkapiException("Data out of range");
        }

        do {
            if ((tester & 1) == 0) {
                inter += "W";
                tester = (tester - 2) / 2;
            } else {
                inter += "N";
                tester = (tester - 1) / 2;
            }
        } while (tester != 0);

        for (i = inter.length() - 1; i >= 0; i--) {
            if (inter.charAt(i) == 'W') {
                dest += "32";
            } else {
                dest += "12";
            }
        }

        readable = "";
        pattern = new String[1];
        pattern[0] = dest;
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }
}
 public class Pharmacode2Track extends Symbol {

    @Override
    protected void encode() {
        int i, tester = 0;
        String inter, dest;

        if (content.length() > 8) {
            throw new OkapiException("Input too long");
        }

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

        for (i = 0; i < content.length(); i++) {
            tester *= 10;
            tester += Character.getNumericValue(content.charAt(i));
        }

        if (tester < 4 || tester > 64570080) {
            throw new OkapiException("Data out of range");
        }

        inter = "";
        do {
            switch (tester % 3) {
            case 0:
                inter += "F";
                tester = (tester - 3) / 3;
                break;
            case 1:
                inter += "D";
                tester = (tester - 1) / 3;
                break;
            case 2:
                inter += "A";
                tester = (tester - 2) / 3;
                break;
            }
        }
        while (tester != 0);

        dest = "";
        for (i = (inter.length() - 1); i >= 0; i--) {
            dest += inter.charAt(i);
        }

        infoLine("Encoding: " + dest);

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

    @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 'A':
                y = 0;
                h = default_height / 2;
                break;
            case 'D':
                y = default_height / 2;
                h = default_height / 2;
                break;
            case 'F':
                y = 0;
                h = default_height;
                break;
            }

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

            x += 2;
        }
        symbol_width = pattern[0].length() * 2;
        symbol_height = default_height;
    }
}
 public class Pharmazentralnummer extends Symbol {

    /* Pharmazentral Nummer is a Code 3 of 9 symbol with an extra
     * check digit. Now generates PZN-8.
     */

    @Override
    protected void encode() {
        int l = content.length();
        String localstr;
        int zeroes, count = 0, check_digit;
        Code3Of9 c = new Code3Of9();

        if (l > 7) {
            throw new OkapiException("Input data too long");
        }

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

        localstr = "-";
        zeroes = 7 - l + 1;
        for (int i = 1; i < zeroes; i++)
            localstr += '0';

        localstr += content;

        for (int i = 1; i < 8; i++) {
            count += i * Character.getNumericValue(localstr.charAt(i));
        }

        check_digit = count % 11;
        if (check_digit == 11) {
            check_digit = 0;
        }
        if (check_digit == 10) {
            throw new OkapiException("Not a valid PZN identifier");
        }

        infoLine("Check Digit: " + check_digit);

        localstr += (char)(check_digit + '0');

        c.setContent(localstr);

        readable = "PZN" + localstr;
        pattern = new String[1];
        pattern[0] = c.pattern[0];
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }
}
 public class Postnet extends Symbol {

    public static enum Mode {
        PLANET, POSTNET
    }

    private static final String[] PN_TABLE = {
        "LLSSS", "SSSLL", "SSLSL", "SSLLS", "SLSSL", "SLSLS", "SLLSS", "LSSSL", "LSSLS", "LSLSS"
    };

    private static final String[] PL_TABLE = {
        "SSLLL", "LLLSS", "LLSLS", "LLSSL", "LSLLS", "LSLSL", "LSSLL", "SLLLS", "SLLSL", "SLSLL"
    };

    private Mode mode;
    private double moduleWidthRatio;

    public Postnet() {
        this.mode = Mode.POSTNET;
        this.moduleWidthRatio = 1.5;
        this.default_height = 12;
        this.humanReadableLocation = HumanReadableLocation.NONE;
    }

    /**
     * Sets the barcode mode (PLANET or POSTNET). The default mode is POSTNET.
     *
     * @param mode the barcode mode (PLANET or POSTNET)
     */
    public void setMode(Mode mode) {
        this.mode = mode;
    }

    /**
     * Returns the barcode mode (PLANET or POSTNET). The default mode is POSTNET.
     *
     * @return the barcode mode (PLANET or POSTNET)
     */
    public Mode getMode() {
        return mode;
    }

    /**
     * Sets the ratio of space width to bar width. The default value is {@code 1.5} (spaces are 50% wider than bars).
     *
     * @param moduleWidthRatio the ratio of space width to bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of space width to bar width.
     *
     * @return the ratio of space width to bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    @Override
    protected void encode() {
        String[] table = (mode == Mode.POSTNET ? PN_TABLE : PL_TABLE);
        encode(table);
    }

    private void encode(String[] table) {
        int i, sum, check_digit;
        String dest;

        if (content.length() > 38) {
            throw new OkapiException("Input too long");
        }

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

        sum = 0;
        dest = "L";

        for (i = 0; i < content.length(); i++) {
            dest += table[content.charAt(i) - '0'];
            sum += content.charAt(i) - '0';
        }

        check_digit = (10 - (sum % 10)) % 10;
        infoLine("Check Digit: " + check_digit);

        dest += table[check_digit];
        dest += "L";

        infoLine("Encoding: " + dest);
        readable = content;
        pattern = new String[] { dest };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    @Override
    protected void plotSymbol() {
        int xBlock, shortHeight;
        double x, y, w, h, dx;

        rectangles.clear();
        texts.clear();

        int baseY;
        if (humanReadableLocation == TOP) {
            baseY = getTheoreticalHumanReadableHeight();
        } else {
            baseY = 0;
        }

        x = 0;
        w = moduleWidth;
        dx = (1 + moduleWidthRatio) * w;
        shortHeight = (int) (0.4 * default_height);
        for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {
            if (pattern[0].charAt(xBlock) == 'L') {
                y = baseY;
                h = default_height;
            } else {
                y = baseY + default_height - shortHeight;
                h = shortHeight;
            }
            rectangles.add(new Rectangle2D.Double(x, y, w, h));
            x += dx;
        }

        symbol_width = (int) Math.ceil(((pattern[0].length() - 1) * dx) + w); // final bar doesn't need extra whitespace
        symbol_height = default_height;

        if (humanReadableLocation != NONE && !readable.isEmpty()) {
            double baseline;
            if (humanReadableLocation == TOP) {
                baseline = fontSize;
            } else {
                baseline = symbol_height + fontSize;
            }
            texts.add(new TextBox(0, baseline, symbol_width, readable, humanReadableAlignment));
        }
    }
}
 public class QrCode extends Symbol {

    public enum EccLevel {
        L, M, Q, H
    }

    private enum QrMode {
        NULL, KANJI, BINARY, ALPHANUM, NUMERIC
    }

    /* Table 5 - Encoding/Decoding table for Alphanumeric mode */
    private static final char[] RHODIUM = {
        '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', ' ', '$', '%', '*', '+', '-', '.', '/', ':'
    };

    private static final int[] QR_DATA_CODEWORDS_L = {
        19, 34, 55, 80, 108, 136, 156, 194, 232, 274, 324, 370, 428, 461, 523, 589, 647,
        721, 795, 861, 932, 1006, 1094, 1174, 1276, 1370, 1468, 1531, 1631,
        1735, 1843, 1955, 2071, 2191, 2306, 2434, 2566, 2702, 2812, 2956
    };

    private static final int[] QR_DATA_CODEWORDS_M = {
        16, 28, 44, 64, 86, 108, 124, 154, 182, 216, 254, 290, 334, 365, 415, 453, 507,
        563, 627, 669, 714, 782, 860, 914, 1000, 1062, 1128, 1193, 1267,
        1373, 1455, 1541, 1631, 1725, 1812, 1914, 1992, 2102, 2216, 2334
    };

    private static final int[] QR_DATA_CODEWORDS_Q = {
        13, 22, 34, 48, 62, 76, 88, 110, 132, 154, 180, 206, 244, 261, 295, 325, 367,
        397, 445, 485, 512, 568, 614, 664, 718, 754, 808, 871, 911,
        985, 1033, 1115, 1171, 1231, 1286, 1354, 1426, 1502, 1582, 1666
    };

    private static final int[] QR_DATA_CODEWORDS_H = {
        9, 16, 26, 36, 46, 60, 66, 86, 100, 122, 140, 158, 180, 197, 223, 253, 283,
        313, 341, 385, 406, 442, 464, 514, 538, 596, 628, 661, 701,
        745, 793, 845, 901, 961, 986, 1054, 1096, 1142, 1222, 1276
    };

    private static final int[] QR_BLOCKS_L = {
        1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12,
        12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25
    };

    private static final int[] QR_BLOCKS_M = {
        1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20,
        21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49
    };

    private static final int[] QR_BLOCKS_Q = {
        1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25,
        27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68
    };

    private static final int[] QR_BLOCKS_H = {
        1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30,
        32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81
    };

    private static final int[] QR_TOTAL_CODEWORDS = {
        26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815,
        901, 991, 1085, 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051,
        2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706
    };

    private static final int[] QR_SIZES = {
        21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97,
        101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177
    };

    private static final int[] QR_ALIGN_LOOPSIZE = {
        0, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7
    };

    private static final int[] QR_TABLE_E1 = {
        6, 18, 0, 0, 0, 0, 0,
        6, 22, 0, 0, 0, 0, 0,
        6, 26, 0, 0, 0, 0, 0,
        6, 30, 0, 0, 0, 0, 0,
        6, 34, 0, 0, 0, 0, 0,
        6, 22, 38, 0, 0, 0, 0,
        6, 24, 42, 0, 0, 0, 0,
        6, 26, 46, 0, 0, 0, 0,
        6, 28, 50, 0, 0, 0, 0,
        6, 30, 54, 0, 0, 0, 0,
        6, 32, 58, 0, 0, 0, 0,
        6, 34, 62, 0, 0, 0, 0,
        6, 26, 46, 66, 0, 0, 0,
        6, 26, 48, 70, 0, 0, 0,
        6, 26, 50, 74, 0, 0, 0,
        6, 30, 54, 78, 0, 0, 0,
        6, 30, 56, 82, 0, 0, 0,
        6, 30, 58, 86, 0, 0, 0,
        6, 34, 62, 90, 0, 0, 0,
        6, 28, 50, 72, 94, 0, 0,
        6, 26, 50, 74, 98, 0, 0,
        6, 30, 54, 78, 102, 0, 0,
        6, 28, 54, 80, 106, 0, 0,
        6, 32, 58, 84, 110, 0, 0,
        6, 30, 58, 86, 114, 0, 0,
        6, 34, 62, 90, 118, 0, 0,
        6, 26, 50, 74, 98, 122, 0,
        6, 30, 54, 78, 102, 126, 0,
        6, 26, 52, 78, 104, 130, 0,
        6, 30, 56, 82, 108, 134, 0,
        6, 34, 60, 86, 112, 138, 0,
        6, 30, 58, 86, 114, 142, 0,
        6, 34, 62, 90, 118, 146, 0,
        6, 30, 54, 78, 102, 126, 150,
        6, 24, 50, 76, 102, 128, 154,
        6, 28, 54, 80, 106, 132, 158,
        6, 32, 58, 84, 110, 136, 162,
        6, 26, 54, 82, 110, 138, 166,
        6, 30, 58, 86, 114, 142, 170
    };

    private static final int[] QR_ANNEX_C = {
        /* Format information bit sequences */
        0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, 0x77c4, 0x72f3, 0x7daa, 0x789d,
        0x662f, 0x6318, 0x6c41, 0x6976, 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b,
        0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed
    };

    private static final int[] QR_ANNEX_D = {
        /* Version information bit sequences */
        0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78,
        0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab,
        0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b,
        0x2542e, 0x26a64, 0x27541, 0x28c69
    };

    private int preferredVersion;
    private EccLevel preferredEccLevel = EccLevel.L;

    /**
     * Sets the preferred symbol size / version. This value may be ignored if the data
     * string is too large to fit into the specified symbol. Input values correspond
     * to symbol sizes as shown in the following table:
     *
     * <table>
     * <tbody>
     * <tr><th>Input</th><th>Symbol Size</th><th>Input</th><th>Symbol Size</th></tr>
     * <tr><td>1    </td><td>21 x 21    </td><td>21   </td><td>101 x 101  </td></tr>
     * <tr><td>2    </td><td>25 x 25    </td><td>22   </td><td>105 x 105  </td></tr>
     * <tr><td>3    </td><td>29 x 29    </td><td>23   </td><td>109 x 109  </td></tr>
     * <tr><td>4    </td><td>33 x 33    </td><td>24   </td><td>113 x 113  </td></tr>
     * <tr><td>5    </td><td>37 x 37    </td><td>25   </td><td>117 x 117  </td></tr>
     * <tr><td>6    </td><td>41 x 41    </td><td>26   </td><td>121 x 121  </td></tr>
     * <tr><td>7    </td><td>45 x 45    </td><td>27   </td><td>125 x 125  </td></tr>
     * <tr><td>8    </td><td>49 x 49    </td><td>28   </td><td>129 x 129  </td></tr>
     * <tr><td>9    </td><td>53 x 53    </td><td>29   </td><td>133 x 133  </td></tr>
     * <tr><td>10   </td><td>57 x 57    </td><td>30   </td><td>137 x 137  </td></tr>
     * <tr><td>11   </td><td>61 x 61    </td><td>31   </td><td>141 x 141  </td></tr>
     * <tr><td>12   </td><td>65 x 65    </td><td>32   </td><td>145 x 145  </td></tr>
     * <tr><td>13   </td><td>69 x 69    </td><td>33   </td><td>149 x 149  </td></tr>
     * <tr><td>14   </td><td>73 x 73    </td><td>34   </td><td>153 x 153  </td></tr>
     * <tr><td>15   </td><td>77 x 77    </td><td>35   </td><td>157 x 157  </td></tr>
     * <tr><td>16   </td><td>81 x 81    </td><td>36   </td><td>161 x 161  </td></tr>
     * <tr><td>17   </td><td>85 x 85    </td><td>37   </td><td>165 x 165  </td></tr>
     * <tr><td>18   </td><td>89 x 89    </td><td>38   </td><td>169 x 169  </td></tr>
     * <tr><td>19   </td><td>93 x 93    </td><td>39   </td><td>173 x 173  </td></tr>
     * <tr><td>20   </td><td>97 x 97    </td><td>40   </td><td>177 x 177  </td></tr>
     * </tbody>
     * </table>
     *
     * @param version the preferred symbol version
     */
    public void setPreferredVersion(int version) {
        preferredVersion = version;
    }

    /**
     * Returns the preferred symbol version.
     *
     * @return the preferred symbol version
     */
    public int getPreferredVersion() {
        return preferredVersion;
    }

    /**
     * Sets the preferred amount of symbol space allocated to error correction. This value may
     * be ignored if there is room for a higher error correction level. Levels are predefined
     * according to the following table:
     *
     * <table>
     * <tbody>
     * <tr><th>ECC Level  </th><th>Error Correction Capacity</th><th>Recovery Capacity</th></tr>
     * <tr><td>L (default)</td><td>Approx 20% of symbol     </td><td>Approx 7%        </td></tr>
     * <tr><td>M          </td><td>Approx 37% of symbol     </td><td>Approx 15%       </td></tr>
     * <tr><td>Q          </td><td>Approx 55% of symbol     </td><td>Approx 25%       </td></tr>
     * <tr><td>H          </td><td>Approx 65% of symbol     </td><td>Approx 30%       </td></tr>
     * </tbody>
     * </table>
     *
     * @param preferredEccLevel the preferred error correction level
     */
    public void setPreferredEccLevel(EccLevel preferredEccLevel) {
        this.preferredEccLevel = preferredEccLevel;
    }

    /**
     * Returns the preferred amount of symbol space allocated to error correction.
     *
     * @return the preferred amount of symbol space allocated to error correction
     */
    public EccLevel getPreferredEccLevel() {
        return this.preferredEccLevel;
    }

    @Override
    protected boolean gs1Supported() {
        return true;
    }

    @Override
    protected void encode() {

        int i, j;
        int est_binlen;
        EccLevel ecc_level;
        int max_cw;
        int targetCwCount, version, blocks;
        int size;
        int bitmask;
        boolean gs1 = (inputDataType == DataType.GS1);

        eciProcess(); // Get ECI mode

        if (eciMode == 20) {
            /* Shift-JIS encoding, use Kanji mode */
            Charset c = Charset.forName("Shift_JIS");
            inputData = new int[content.length()];
            for (i = 0; i < inputData.length; i++) {
                CharBuffer buffer = CharBuffer.wrap(content, i, i + 1);
                byte[] bytes = c.encode(buffer).array();
                int value = (bytes.length == 2 ? ((bytes[0] & 0xff) << 8) | (bytes[1] & 0xff) : bytes[0]);
                inputData[i] = value;
            }
        } else {
            /* inputData already initialized in eciProcess() */
        }

        QrMode[] inputMode = new QrMode[inputData.length];
        defineMode(inputMode, inputData);
        est_binlen = getBinaryLength(40, inputMode, inputData, gs1, eciMode);

        ecc_level = this.preferredEccLevel;
        switch (this.preferredEccLevel) {
            case L:
            default:
                max_cw = 2956;
                break;
            case M:
                max_cw = 2334;
                break;
            case Q:
                max_cw = 1666;
                break;
            case H:
                max_cw = 1276;
                break;
        }

        if (est_binlen > (8 * max_cw)) {
            throw new OkapiException("Input too long for selected error correction level");
        }

        // ZINT NOTE: this block is different from the corresponding block of code in Zint;
        // it is simplified, but the simplification required that the applyOptimisation method
        // be changed to be free of side effects (by putting the optimized mode array into a
        // new array instead of modifying the existing array)

        version = 40;
        for (i = 39; i >= 0; i--) {
            int[] dataCodewords;
            switch (ecc_level) {
                case L:
                default:
                    dataCodewords = QR_DATA_CODEWORDS_L;
                    break;
                case M:
                    dataCodewords = QR_DATA_CODEWORDS_M;
                    break;
                case Q:
                    dataCodewords = QR_DATA_CODEWORDS_Q;
                    break;
                case H:
                    dataCodewords = QR_DATA_CODEWORDS_H;
                    break;
            }
            int proposedVersion = i + 1;
            int proposedBinLen = getBinaryLength(proposedVersion, inputMode, inputData, gs1, eciMode);
            if ((8 * dataCodewords[i]) >= proposedBinLen) {
                version = proposedVersion;
                est_binlen = proposedBinLen;
            }
        }

        inputMode = applyOptimisation(version, inputMode);

        // ZINT NOTE: end of block of code that is different

// TODO: delete this
//
//        autosize = 40;
//        for (i = 39; i >= 0; i--) {
//            switch (ecc_level) {
//                case L:
//                    if ((8 * QR_DATA_CODEWORDS_L[i]) >= est_binlen) {
//                        autosize = i + 1;
//                    }
//                    break;
//                case M:
//                    if ((8 * QR_DATA_CODEWORDS_M[i]) >= est_binlen) {
//                        autosize = i + 1;
//                    }
//                    break;
//                case Q:
//                    if ((8 * QR_DATA_CODEWORDS_Q[i]) >= est_binlen) {
//                        autosize = i + 1;
//                    }
//                    break;
//                case H:
//                    if ((8 * QR_DATA_CODEWORDS_H[i]) >= est_binlen) {
//                        autosize = i + 1;
//                    }
//                    break;
//            }
//        }
//
//        // Now see if the optimized binary will fit in a smaller symbol.
//        canShrink = true;
//
//        do {
//            if (autosize == 1) {
//                est_binlen = getBinaryLength(autosize, inputMode, inputData, gs1, eciMode); // TODO: added
//                canShrink = false;
//            } else {
//                est_binlen = getBinaryLength(autosize - 1, inputMode, inputData, gs1, eciMode);
//
//                switch (ecc_level) {
//                    case L:
//                        if ((8 * QR_DATA_CODEWORDS_L[autosize - 2]) < est_binlen) {
//                            canShrink = false;
//                        }
//                        break;
//                    case M:
//                        if ((8 * QR_DATA_CODEWORDS_M[autosize - 2]) < est_binlen) {
//                            canShrink = false;
//                        }
//                        break;
//                    case Q:
//                        if ((8 * QR_DATA_CODEWORDS_Q[autosize - 2]) < est_binlen) {
//                            canShrink = false;
//                        }
//                        break;
//                    case H:
//                        if ((8 * QR_DATA_CODEWORDS_H[autosize - 2]) < est_binlen) {
//                            canShrink = false;
//                        }
//                        break;
//                }
//
//                if (canShrink) {
//                    // Optimization worked - data will fit in a smaller symbol
//                    autosize--;
//                } else {
//                    // Data did not fit in the smaller symbol, revert to original size
//                    est_binlen = getBinaryLength(autosize, inputMode, inputData, gs1, eciMode);
//                }
//            }
//        } while (canShrink);
//
//        version = autosize;

        if ((preferredVersion >= 1) && (preferredVersion <= 40)) {
            /* If the user has selected a larger symbol than the smallest available,
             then use the size the user has selected, and re-optimize for this
             symbol size.
             */
            if (preferredVersion > version) {
                version = preferredVersion;
                est_binlen = getBinaryLength(preferredVersion, inputMode, inputData, gs1, eciMode);
                inputMode = applyOptimisation(version, inputMode);
            }
            if (preferredVersion < version) {
                throw new OkapiException("Input too long for selected symbol size");
            }
        }

        /* Ensure maximum error correction capacity */
        if (est_binlen <= (QR_DATA_CODEWORDS_M[version - 1] * 8)) {
            ecc_level = EccLevel.M;
        }
        if (est_binlen <= (QR_DATA_CODEWORDS_Q[version - 1] * 8)) {
            ecc_level = EccLevel.Q;
        }
        if (est_binlen <= (QR_DATA_CODEWORDS_H[version - 1] * 8)) {
            ecc_level = EccLevel.H;
        }

        targetCwCount = QR_DATA_CODEWORDS_L[version - 1];
        blocks = QR_BLOCKS_L[version - 1];
        switch (ecc_level) {
            case M:
                targetCwCount = QR_DATA_CODEWORDS_M[version - 1];
                blocks = QR_BLOCKS_M[version - 1];
                break;
            case Q:
                targetCwCount = QR_DATA_CODEWORDS_Q[version - 1];
                blocks = QR_BLOCKS_Q[version - 1];
                break;
            case H:
                targetCwCount = QR_DATA_CODEWORDS_H[version - 1];
                blocks = QR_BLOCKS_H[version - 1];
                break;
        }

        int[] datastream = new int[targetCwCount + 1];
        int[] fullstream = new int[QR_TOTAL_CODEWORDS[version - 1] + 1];

        qrBinary(datastream, version, targetCwCount, inputMode, inputData, gs1, eciMode, est_binlen);
        addEcc(fullstream, datastream, version, targetCwCount, blocks);

        size = QR_SIZES[version - 1];

        int[] grid = new int[size * size];

        infoLine("Version: " + version);
        infoLine("ECC Level: " + ecc_level.name());

        setupGrid(grid, size, version);
        populateGrid(grid, size, fullstream, QR_TOTAL_CODEWORDS[version - 1]);

        if (version >= 7) {
            addVersionInfo(grid, size, version);
        }

        bitmask = applyBitmask(grid, size, ecc_level);
        infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask));
        addFormatInfo(grid, size, ecc_level, bitmask);

        readable = "";
        pattern = new String[size];
        row_count = size;
        row_height = new int[size];
        for (i = 0; i < size; i++) {
            StringBuilder bin = new StringBuilder(size);
            for (j = 0; j < size; j++) {
                if ((grid[(i * size) + j] & 0x01) != 0) {
                    bin.append('1');
                } else {
                    bin.append('0');
                }
            }
            pattern[i] = bin2pat(bin);
            row_height[i] = 1;
        }
    }

    /** Place Kanji / Binary / Alphanumeric / Numeric values in inputMode. */
    private static void defineMode(QrMode[] inputMode, int[] inputData) {

        int mlen;

        for (int i = 0; i < inputData.length; i++) {
            if (inputData[i] > 0xff) {
                inputMode[i] = QrMode.KANJI;
            } else {
                inputMode[i] = QrMode.BINARY;
                if (isAlpha(inputData[i])) {
                    inputMode[i] = QrMode.ALPHANUM;
                }
                if (inputData[i] == FNC1) {
                    inputMode[i] = QrMode.ALPHANUM;
                }
                if (isNumeric(inputData[i])) {
                    inputMode[i] = QrMode.NUMERIC;
                }
            }
        }

        // TODO: uncomment
//        /* If less than 6 numeric digits together then don't use numeric mode */
//        for (int i = 0; i < inputMode.length; i++) {
//            if (inputMode[i] == QrMode.NUMERIC) {
//                if (((i != 0) && (inputMode[i - 1] != QrMode.NUMERIC)) || (i == 0)) {
//                    mlen = 0;
//                    while (((mlen + i) < inputMode.length) && (inputMode[mlen + i] == QrMode.NUMERIC)) {
//                        mlen++;
//                    };
//                    if (mlen < 6) {
//                        for (int j = 0; j < mlen; j++) {
//                            inputMode[i + j] = QrMode.ALPHANUM;
//                        }
//                    }
//                }
//            }
//        }
//
//        /* If less than 4 alphanumeric characters together then don't use alphanumeric mode */
//        for (int i = 0; i < inputMode.length; i++) {
//            if (inputMode[i] == QrMode.ALPHANUM) {
//                if (((i != 0) && (inputMode[i - 1] != QrMode.ALPHANUM)) || (i == 0)) {
//                    mlen = 0;
//                    while (((mlen + i) < inputMode.length) && (inputMode[mlen + i] == QrMode.ALPHANUM)) {
//                        mlen++;
//                    };
//                    if (mlen < 4) {
//                        for (int j = 0; j < mlen; j++) {
//                            inputMode[i + j] = QrMode.BINARY;
//                        }
//                    }
//                }
//            }
//        }
    }

    /** Calculate the actual bit length of the proposed binary string. */
    private static int getBinaryLength(int version, QrMode[] inputModeUnoptimized, int[] inputData, boolean gs1, int eciMode) {

        int i, j;
        QrMode currentMode;
        int inputLength = inputModeUnoptimized.length;
        int count = 0;
        int alphaLength;
        int percent = 0;

        // ZINT NOTE: in Zint, this call modifies the input mode array directly; here, we leave
        // the original array alone so that subsequent binary length checks don't irrevocably
        // optimize the mode array for the wrong QR Code version
        QrMode[] inputMode = applyOptimisation(version, inputModeUnoptimized);

        currentMode = QrMode.NULL;

        if (gs1) {
            count += 4;
        }

        if (eciMode != 3) {
            count += 12;
        }

        for (i = 0; i < inputLength; i++) {
            if (inputMode[i] != currentMode) {
                count += 4;
                switch (inputMode[i]) {
                    case KANJI:
                        count += tribus(version, 8, 10, 12);
                        count += (blockLength(i, inputMode) * 13);
                        break;
                    case BINARY:
                        count += tribus(version, 8, 16, 16);
                        for (j = i; j < (i + blockLength(i, inputMode)); j++) {
                            if (inputData[j] > 0xff) {
                                count += 16;
                            } else {
                                count += 8;
                            }
                        }
                        break;
                    case ALPHANUM:
                        count += tribus(version, 9, 11, 13);
                        alphaLength = blockLength(i, inputMode);
                        // In alphanumeric mode % becomes %%
                        if (gs1) {
                            for (j = i; j < (i + alphaLength); j++) { // TODO: need to do this only if in GS1 mode? or is the other code wrong? https://sourceforge.net/p/zint/tickets/104/#227b
                                if (inputData[j] == '%') {
                                    percent++;
                                }
                            }
                        }
                        alphaLength += percent;
                        switch (alphaLength % 2) {
                            case 0:
                                count += (alphaLength / 2) * 11;
                                break;
                            case 1:
                                count += ((alphaLength - 1) / 2) * 11;
                                count += 6;
                                break;
                        }
                        break;
                    case NUMERIC:
                        count += tribus(version, 10, 12, 14);
                        switch (blockLength(i, inputMode) % 3) {
                            case 0:
                                count += (blockLength(i, inputMode) / 3) * 10;
                                break;
                            case 1:
                                count += ((blockLength(i, inputMode) - 1) / 3) * 10;
                                count += 4;
                                break;
                            case 2:
                                count += ((blockLength(i, inputMode) - 2) / 3) * 10;
                                count += 7;
                                break;
                        }
                        break;
                }
                currentMode = inputMode[i];
            }
        }

        return count;
    }

    /**
     * Implements a custom optimization algorithm, because implementation of the algorithm
     * shown in Annex J.2 created LONGER binary sequences.
     */
    private static QrMode[] applyOptimisation(int version, QrMode[] inputMode) {

        int inputLength = inputMode.length;
        int blockCount = 0;
        int i, j;
        QrMode currentMode = QrMode.NULL;

        for (i = 0; i < inputLength; i++) {
            if (inputMode[i] != currentMode) {
                currentMode = inputMode[i];
                blockCount++;
            }
        }

        int[] blockLength = new int[blockCount];
        QrMode[] blockMode = new QrMode[blockCount];

        j = -1;
        currentMode = QrMode.NULL;
        for (i = 0; i < inputLength; i++) {
            if (inputMode[i] != currentMode) {
                j++;
                blockLength[j] = 1;
                blockMode[j] = inputMode[i];
                currentMode = inputMode[i];
            } else {
                blockLength[j]++;
            }
        }

        if (blockCount > 1) {
            // Search forward
            for (i = 0; i <= (blockCount - 2); i++) {
                if (blockMode[i] == QrMode.BINARY) {
                    switch (blockMode[i + 1]) {
                        case KANJI:
                            if (blockLength[i + 1] < tribus(version, 4, 5, 6)) {
                                blockMode[i + 1] = QrMode.BINARY;
                            }
                            break;
                        case ALPHANUM:
                            if (blockLength[i + 1] < tribus(version, 7, 8, 9)) {
                                blockMode[i + 1] = QrMode.BINARY;
                            }
                            break;
                        case NUMERIC:
                            if (blockLength[i + 1] < tribus(version, 3, 4, 5)) {
                                blockMode[i + 1] = QrMode.BINARY;
                            }
                            break;
                    }
                }

                if ((blockMode[i] == QrMode.ALPHANUM)
                        && (blockMode[i + 1] == QrMode.NUMERIC)) {
                    if (blockLength[i + 1] < tribus(version, 6, 8, 10)) {
                        blockMode[i + 1] = QrMode.ALPHANUM;
                    }
                }
            }

            // Search backward
            for (i = blockCount - 1; i > 0; i--) {
                if (blockMode[i] == QrMode.BINARY) {
                    switch (blockMode[i - 1]) {
                        case KANJI:
                            if (blockLength[i - 1] < tribus(version, 4, 5, 6)) {
                                blockMode[i - 1] = QrMode.BINARY;
                            }
                            break;
                        case ALPHANUM:
                            if (blockLength[i - 1] < tribus(version, 7, 8, 9)) {
                                blockMode[i - 1] = QrMode.BINARY;
                            }
                            break;
                        case NUMERIC:
                            if (blockLength[i - 1] < tribus(version, 3, 4, 5)) {
                                blockMode[i - 1] = QrMode.BINARY;
                            }
                            break;
                    }
                }

                if ((blockMode[i] == QrMode.ALPHANUM)
                        && (blockMode[i - 1] == QrMode.NUMERIC)) {
                    if (blockLength[i - 1] < tribus(version, 6, 8, 10)) {
                        blockMode[i - 1] = QrMode.ALPHANUM;
                    }
                }
            }
        }

        // ZINT NOTE: this method is different from the original Zint code in that it creates a
        // new array to hold the optimized values and returns it, rather than modifying the
        // original array; this allows this method to be called as many times as we want without
        // worrying about side effects

        QrMode[] optimized = new QrMode[inputMode.length];

        j = 0;
        for (int block = 0; block < blockCount; block++) {
            currentMode = blockMode[block];
            for (i = 0; i < blockLength[block]; i++) {
                optimized[j] = currentMode;
                j++;
            }
        }

        return optimized;
    }

    /** Find the length of the block starting from 'start'. */
    private static int blockLength(int start, QrMode[] inputMode) {

        QrMode mode = inputMode[start];
        int count = 0;
        int i = start;

        do {
            count++;
        } while ((i + count) < inputMode.length && inputMode[i + count] == mode);

        return count;
    }

    /** Choose from three numbers based on version. */
    private static int tribus(int version, int a, int b, int c) {
        if (version < 10) {
            return a;
        } else if (version >= 10 && version <= 26) {
            return b;
        } else {
            return c;
        }
    }

    /** Returns true if input is in the Alphanumeric set (see Table J.1) */
    private static boolean isAlpha(int c) {
        return (c >= '0' && c <= '9') ||
               (c >= 'A' && c <= 'Z') ||
               (c == ' ') ||
               (c == '$') ||
               (c == '%') ||
               (c == '*') ||
               (c == '+') ||
               (c == '-') ||
               (c == '.') ||
               (c == '/') ||
               (c == ':');
    }

    /** Returns true if input is in the Numeric set (see Table J.1) */
    private static boolean isNumeric(int c) {
        return (c >= '0' && c <= '9');
    }

    /** Converts input data to a binary stream and adds padding. */
    private void qrBinary(int[] datastream, int version, int target_binlen, QrMode[] inputMode, int[] inputData, boolean gs1, int eciMode, int est_binlen) {

        // TODO: make encodeInfo a StringBuilder, make this method static?

        int position = 0;
        int short_data_block_length, i;
        int padbits;
        int current_binlen, current_bytes;
        int toggle;
        QrMode data_block;

        StringBuilder binary = new StringBuilder(est_binlen + 12);

        if (gs1) {
            binary.append("0101"); /* FNC1 */
        }

        if (eciMode != 3) {
            binary.append("0111"); /* ECI (Table 4) */
            if (eciMode <= 127) {
                binaryAppend(eciMode, 8, binary); /* 000000 to 000127 */
            } else if (eciMode <= 16383) {
                binaryAppend(0x8000 + eciMode, 16, binary); /* 000000 to 016383 */
            } else {
                binaryAppend(0xC00000 + eciMode, 24, binary); /* 000000 to 999999 */
            }
        }

        info("Encoding: ");

        do {
            data_block = inputMode[position];
            short_data_block_length = 0;
            do {
                short_data_block_length++;
            } while (((short_data_block_length + position) < inputMode.length)
                    && (inputMode[position + short_data_block_length] == data_block));

            switch (data_block) {

                case KANJI:
                    /* Kanji mode */
                    /* Mode indicator */
                    binary.append("1000");

                    /* Character count indicator */
                    binaryAppend(short_data_block_length, tribus(version, 8, 10, 12), binary);

                    info("KNJI ");

                    /* Character representation */
                    for (i = 0; i < short_data_block_length; i++) {
                        int jis = inputData[position + i];
                        if (jis >= 0x8140 && jis <= 0x9ffc) {
                            jis -= 0x8140;
                        } else if (jis >= 0xe040 && jis <= 0xebbf) {
                            jis -= 0xc140;
                        }
                        int prod = ((jis >> 8) * 0xc0) + (jis & 0xff);
                        binaryAppend(prod, 13, binary);
                        infoSpace(prod);
                    }

                    break;

                case BINARY:
                    /* Byte mode */
                    /* Mode indicator */
                    binary.append("0100");

                    /* Character count indicator */
                    binaryAppend(short_data_block_length, tribus(version, 8, 16, 16), binary);

                    info("BYTE ");

                    /* Character representation */
                    for (i = 0; i < short_data_block_length; i++) {
                        int b = inputData[position + i];
                        if (b == FNC1) {
                            b = 0x1d; /* FNC1 */
                        }
                        binaryAppend(b, 8, binary);
                        infoSpace(b);
                    }

                    break;

                case ALPHANUM:
                    /* Alphanumeric mode */
                    /* Mode indicator */
                    binary.append("0010");

                    /* If in GS1 mode, expand FNC1 -> '%' and expand '%' -> '%%' in a new array */
                    int percentCount = 0;
                    if (gs1) {
                        for (i = 0; i < short_data_block_length; i++) {
                            if (inputData[position + i] == '%') {
                                percentCount++;
                            }
                        }
                    }
                    int[] inputExpanded = new int[short_data_block_length + percentCount];
                    percentCount = 0;
                    for (i = 0; i < short_data_block_length; i++) {
                        int c = inputData[position + i];
                        if (c == FNC1) {
                            inputExpanded[i + percentCount] = '%'; /* FNC1 */
                        } else {
                            inputExpanded[i + percentCount] = c;
                            if (gs1 && c == '%') {
                                percentCount++;
                                inputExpanded[i + percentCount] = c;
                            }
                        }
                    }

                    /* Character count indicator */
                    binaryAppend(inputExpanded.length, tribus(version, 9, 11, 13), binary);

                    info("ALPH ");

                    /* Character representation */
                    for (i = 0; i + 1 < inputExpanded.length; i += 2) {
                        int first = positionOf((char) inputExpanded[i], RHODIUM);
                        int second = positionOf((char) inputExpanded[i + 1], RHODIUM);
                        int prod = (first * 45) + second;
                        int count = 2;
                        binaryAppend(prod, 1 + (5 * count), binary);
                        infoSpace(prod);
                    }
                    if (inputExpanded.length % 2 != 0) {
                        int first = positionOf((char) inputExpanded[inputExpanded.length - 1], RHODIUM);
                        int prod = first;
                        int count = 1;
                        binaryAppend(prod, 1 + (5 * count), binary);
                        infoSpace(prod);
                    }

                    break;

                case NUMERIC:
                    /* Numeric mode */
                    /* Mode indicator */
                    binary.append("0001");

                    /* Character count indicator */
                    binaryAppend(short_data_block_length, tribus(version, 10, 12, 14), binary);

                    info("NUMB ");

                    /* Character representation */
                    i = 0;
                    while (i < short_data_block_length) {

                        int first = Character.getNumericValue(inputData[position + i]);
                        int count = 1;
                        int prod = first;

                        if (i + 1 < short_data_block_length) {
                            int second = Character.getNumericValue(inputData[position + i + 1]);
                            count = 2;
                            prod = (prod * 10) + second;

                            if (i + 2 < short_data_block_length) {
                                int third = Character.getNumericValue(inputData[position + i + 2]);
                                count = 3;
                                prod = (prod * 10) + third;
                            }
                        }

                        binaryAppend(prod, 1 + (3 * count), binary);

                        infoSpace(prod);

                        i += count;
                    }

                    break;
            }

            position += short_data_block_length;

        } while (position < inputMode.length);

        infoLine();

        /* Terminator */
        binary.append("0000");

        current_binlen = binary.length();
        padbits = 8 - (current_binlen % 8);
        if (padbits == 8) {
            padbits = 0;
        }
        current_bytes = (current_binlen + padbits) / 8;

        /* Padding bits */
        for (i = 0; i < padbits; i++) {
            binary.append('0');
        }

        /* Put data into 8-bit codewords */
        for (i = 0; i < current_bytes; i++) {
            datastream[i] = 0x00;
            for (int p = 0; p < 8; p++) {
                if (binary.charAt((i * 8) + p) == '1') {
                    datastream[i] += (0x80 >> p);
                }
            }
        }

        /* Add pad codewords */
        toggle = 0;
        for (i = current_bytes; i < target_binlen; i++) {
            if (toggle == 0) {
                datastream[i] = 0xec;
                toggle = 1;
            } else {
                datastream[i] = 0x11;
                toggle = 0;
            }
        }

        info("Codewords: ");
        for (i = 0; i < target_binlen; i++) {
            infoSpace(datastream[i]);
        }
        infoLine();
    }

    private static void binaryAppend(int value, int length, StringBuilder binary) {
        int start = 0x01 << (length - 1);
        for (int i = 0; i < length; i++) {
            if ((value & (start >> i)) != 0) {
                binary.append('1');
            } else {
                binary.append('0');
            }
        }
    }

    /** Splits data into blocks, adds error correction and then interleaves the blocks and error correction data. */
    private static void addEcc(int[] fullstream, int[] datastream, int version, int data_cw, int blocks) {

        int ecc_cw = QR_TOTAL_CODEWORDS[version - 1] - data_cw;
        int short_data_block_length = data_cw / blocks;
        int qty_long_blocks = data_cw % blocks;
        int qty_short_blocks = blocks - qty_long_blocks;
        int ecc_block_length = ecc_cw / blocks;
        int i, j, length_this_block, posn;

        int[] data_block = new int[short_data_block_length + 2];
        int[] ecc_block = new int[ecc_block_length + 2];
        int[] interleaved_data = new int[data_cw + 2];
        int[] interleaved_ecc = new int[ecc_cw + 2];

        posn = 0;

        for (i = 0; i < blocks; i++) {
            if (i < qty_short_blocks) {
                length_this_block = short_data_block_length;
            } else {
                length_this_block = short_data_block_length + 1;
            }

            for (j = 0; j < ecc_block_length; j++) {
                ecc_block[j] = 0;
            }

            for (j = 0; j < length_this_block; j++) {
                data_block[j] = datastream[posn + j];
            }

            ReedSolomon rs = new ReedSolomon();
            rs.init_gf(0x11d);
            rs.init_code(ecc_block_length, 0);
            rs.encode(length_this_block, data_block);

            for (j = 0; j < ecc_block_length; j++) {
                ecc_block[j] = rs.getResult(j);
            }

            for (j = 0; j < short_data_block_length; j++) {
                interleaved_data[(j * blocks) + i] = data_block[j];
            }

            if (i >= qty_short_blocks) {
                interleaved_data[(short_data_block_length * blocks) + (i - qty_short_blocks)] = data_block[short_data_block_length];
            }

            for (j = 0; j < ecc_block_length; j++) {
                interleaved_ecc[(j * blocks) + i] = ecc_block[ecc_block_length - j - 1];
            }

            posn += length_this_block;
        }

        for (j = 0; j < data_cw; j++) {
            fullstream[j] = interleaved_data[j];
        }
        for (j = 0; j < ecc_cw; j++) {
            fullstream[j + data_cw] = interleaved_ecc[j];
        }
    }

    private static void setupGrid(int[] grid, int size, int version) {

        int i;
        boolean toggle = true;

        /* Add timing patterns */
        for (i = 0; i < size; i++) {
            if (toggle) {
                grid[(6 * size) + i] = 0x21;
                grid[(i * size) + 6] = 0x21;
                toggle = false;
            } else {
                grid[(6 * size) + i] = 0x20;
                grid[(i * size) + 6] = 0x20;
                toggle = true;
            }
        }

        /* Add finder patterns */
        placeFinder(grid, size, 0, 0);
        placeFinder(grid, size, 0, size - 7);
        placeFinder(grid, size, size - 7, 0);

        /* Add separators */
        for (i = 0; i < 7; i++) {
            grid[(7 * size) + i] = 0x10;
            grid[(i * size) + 7] = 0x10;
            grid[(7 * size) + (size - 1 - i)] = 0x10;
            grid[(i * size) + (size - 8)] = 0x10;
            grid[((size - 8) * size) + i] = 0x10;
            grid[((size - 1 - i) * size) + 7] = 0x10;
        }
        grid[(7 * size) + 7] = 0x10;
        grid[(7 * size) + (size - 8)] = 0x10;
        grid[((size - 8) * size) + 7] = 0x10;

        /* Add alignment patterns */
        if (version != 1) {
            /* Version 1 does not have alignment patterns */
            int loopsize = QR_ALIGN_LOOPSIZE[version - 1];
            for (int x = 0; x < loopsize; x++) {
                for (int y = 0; y < loopsize; y++) {
                    int xcoord = QR_TABLE_E1[((version - 2) * 7) + x];
                    int ycoord = QR_TABLE_E1[((version - 2) * 7) + y];
                    if ((grid[(ycoord * size) + xcoord] & 0x10) == 0) {
                        placeAlign(grid, size, xcoord, ycoord);
                    }
                }
            }
        }

        /* Reserve space for format information */
        for (i = 0; i < 8; i++) {
            grid[(8 * size) + i] += 0x20;
            grid[(i * size) + 8] += 0x20;
            grid[(8 * size) + (size - 1 - i)] = 0x20;
            grid[((size - 1 - i) * size) + 8] = 0x20;
        }
        grid[(8 * size) + 8] += 0x20;
        grid[((size - 1 - 7) * size) + 8] = 0x21; /* Dark Module from Figure 25 */

        /* Reserve space for version information */
        if (version >= 7) {
            for (i = 0; i < 6; i++) {
                grid[((size - 9) * size) + i] = 0x20;
                grid[((size - 10) * size) + i] = 0x20;
                grid[((size - 11) * size) + i] = 0x20;
                grid[(i * size) + (size - 9)] = 0x20;
                grid[(i * size) + (size - 10)] = 0x20;
                grid[(i * size) + (size - 11)] = 0x20;
            }
        }
    }

    private static void placeFinder(int[] grid, int size, int x, int y) {

        int[] finder = {
            1, 1, 1, 1, 1, 1, 1,
            1, 0, 0, 0, 0, 0, 1,
            1, 0, 1, 1, 1, 0, 1,
            1, 0, 1, 1, 1, 0, 1,
            1, 0, 1, 1, 1, 0, 1,
            1, 0, 0, 0, 0, 0, 1,
            1, 1, 1, 1, 1, 1, 1
        };

        for (int xp = 0; xp < 7; xp++) {
            for (int yp = 0; yp < 7; yp++) {
                if (finder[xp + (7 * yp)] == 1) {
                    grid[((yp + y) * size) + (xp + x)] = 0x11;
                } else {
                    grid[((yp + y) * size) + (xp + x)] = 0x10;
                }
            }
        }
    }

    private static void placeAlign(int[] grid, int size, int x, int y) {

        int[] alignment = {
            1, 1, 1, 1, 1,
            1, 0, 0, 0, 1,
            1, 0, 1, 0, 1,
            1, 0, 0, 0, 1,
            1, 1, 1, 1, 1
        };

        x -= 2;
        y -= 2; /* Input values represent centre of pattern */

        for (int xp = 0; xp < 5; xp++) {
            for (int yp = 0; yp < 5; yp++) {
                if (alignment[xp + (5 * yp)] == 1) {
                    grid[((yp + y) * size) + (xp + x)] = 0x11;
                } else {
                    grid[((yp + y) * size) + (xp + x)] = 0x10;
                }
            }
        }
    }

    private static void populateGrid(int[] grid, int size, int[] fullstream, int cw) {

        boolean goingUp = true;
        int row = 0; /* right hand side */

        int i, n, y;

        n = cw * 8;
        y = size - 1;
        i = 0;
        do {
            int x = (size - 2) - (row * 2);
            if (x < 6) {
                x--; /* skip over vertical timing pattern */
            }

            if ((grid[(y * size) + (x + 1)] & 0xf0) == 0) {
                if (cwbit(fullstream, i)) {
                    grid[(y * size) + (x + 1)] = 0x01;
                } else {
                    grid[(y * size) + (x + 1)] = 0x00;
                }
                i++;
            }

            if (i < n) {
                if ((grid[(y * size) + x] & 0xf0) == 0) {
                    if (cwbit(fullstream, i)) {
                        grid[(y * size) + x] = 0x01;
                    } else {
                        grid[(y * size) + x] = 0x00;
                    }
                    i++;
                }
            }

            if (goingUp) {
                y--;
            } else {
                y++;
            }
            if (y == -1) {
                /* reached the top */
                row++;
                y = 0;
                goingUp = false;
            }
            if (y == size) {
                /* reached the bottom */
                row++;
                y = size - 1;
                goingUp = true;
            }
        } while (i < n);
    }

    private static boolean cwbit(int[] fullstream, int i) {
        return ((fullstream[i / 8] & (0x80 >> (i % 8))) != 0);
    }

    private static int applyBitmask(int[] grid, int size, EccLevel ecc_level) {

        int x, y;
        char p;
        int pattern;
        int best_val, best_pattern;
        int[] penalty = new int[8];
        byte[] mask = new byte[size * size];
        byte[] eval = new byte[size * size];

        /* Perform data masking */
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                mask[(y * size) + x] = 0x00;
                // all eight bit mask variants are encoded in the 8 bits of the bytes that make up the mask array
                if ((grid[(y * size) + x] & 0xf0) == 0) { // exclude areas not to be masked
                    if (((y + x) & 1) == 0) {
                        mask[(y * size) + x] += (byte) 0x01;
                    }
                    if ((y & 1) == 0) {
                        mask[(y * size) + x] += (byte) 0x02;
                    }
                    if ((x % 3) == 0) {
                        mask[(y * size) + x] += (byte) 0x04;
                    }
                    if (((y + x) % 3) == 0) {
                        mask[(y * size) + x] += (byte) 0x08;
                    }
                    if ((((y / 2) + (x / 3)) & 1) == 0) {
                        mask[(y * size) + x] += (byte) 0x10;
                    }
                    if ((((y * x) & 1) + ((y * x) % 3)) == 0) {
                        mask[(y * size) + x] += (byte) 0x20;
                    }
                    if (((((y * x) & 1) + ((y * x) % 3)) & 1) == 0) {
                        mask[(y * size) + x] += (byte) 0x40;
                    }
                    if (((((y + x) & 1) + ((y * x) % 3)) & 1) == 0) {
                        mask[(y * size) + x] += (byte) 0x80;
                    }
                }
            }
        }

        /* Apply data masks to grid, result in eval */
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                if ((grid[(y * size) + x] & 0x01) != 0) {
                    p = 0xff;
                } else {
                    p = 0x00;
                }
                eval[(y * size) + x] = (byte) (mask[(y * size) + x] ^ p);
            }
        }

        /* Evaluate result */
        for (pattern = 0; pattern < 8; pattern++) {
            addFormatInfoEval(eval, size, ecc_level, pattern);
            penalty[pattern] = evaluate(eval, size, pattern);
        }

        best_pattern = 0;
        best_val = penalty[0];
        for (pattern = 1; pattern < 8; pattern++) {
            if (penalty[pattern] < best_val) {
                best_pattern = pattern;
                best_val = penalty[pattern];
            }
        }

        /* Apply mask */
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                if ((mask[(y * size) + x] & (0x01 << best_pattern)) != 0) {
                    if ((grid[(y * size) + x] & 0x01) != 0) {
                        grid[(y * size) + x] = 0x00;
                    } else {
                        grid[(y * size) + x] = 0x01;
                    }
                }
            }
        }

        return best_pattern;
    }

    /** Adds format information to eval. */
    private static void addFormatInfoEval(byte[] eval, int size, EccLevel ecc_level, int pattern) {

        int format = pattern;
        int seq;
        int i;

        switch(ecc_level) {
            case L: format += 0x08; break;
            case Q: format += 0x18; break;
            case H: format += 0x10; break;
        }

        seq = QR_ANNEX_C[format];

        for (i = 0; i < 6; i++) {
            eval[(i * size) + 8] = (byte) ((((seq >> i) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
        }

        for (i = 0; i < 8; i++) {
            eval[(8 * size) + (size - i - 1)] = (byte) ((((seq >> i) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
        }

        for (i = 0; i < 6; i++) {
            eval[(8 * size) + (5 - i)] = (byte) ((((seq >> (i + 9)) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
        }

        for (i = 0; i < 7; i++) {
            eval[(((size - 7) + i) * size) + 8] = (byte) ((((seq >> (i + 8)) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
        }

        eval[(7 * size) + 8] = (byte) ((((seq >> 6) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
        eval[(8 * size) + 8] = (byte) ((((seq >> 7) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
        eval[(8 * size) + 7] = (byte) ((((seq >> 8) & 0x01) != 0) ? (0x01 >> pattern) : 0x00);
    }

    private static int evaluate(byte[] eval, int size, int pattern) {

        int x, y, block, weight;
        int result = 0;
        int state;
        int p;
        int dark_mods;
        int percentage, k;
        int a, b, afterCount, beforeCount;
        byte[] local = new byte[size * size];

        // all eight bit mask variants have been encoded in the 8 bits of the bytes
        // that make up the grid array; select them for evaluation according to the
        // desired pattern
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                if ((eval[(y * size) + x] & (0x01 << pattern)) != 0) {
                    local[(y * size) + x] = '1';
                } else {
                    local[(y * size) + x] = '0';
                }
            }
        }

        /* Test 1: Adjacent modules in row/column in same colour */
        /* Vertical */
        for (x = 0; x < size; x++) {
            state = local[x];
            block = 0;
            for (y = 0; y < size; y++) {
                if (local[(y * size) + x] == state) {
                    block++;
                } else {
                    if (block > 5) {
                        result += (3 + (block - 5));
                    }
                    block = 0;
                    state = local[(y * size) + x];
                }
            }
            if (block > 5) {
                result += (3 + (block - 5));
            }
        }

        /* Horizontal */
        for (y = 0; y < size; y++) {
            state = local[y * size];
            block = 0;
            for (x = 0; x < size; x++) {
                if (local[(y * size) + x] == state) {
                    block++;
                } else {
                    if (block > 5) {
                        result += (3 + (block - 5));
                    }
                    block = 0;
                    state = local[(y * size) + x];
                }
            }
            if (block > 5) {
                result += (3 + (block - 5));
            }
        }

        /* Test 2: Block of modules in same color */
        for (x = 0; x < size - 1; x++) {
            for (y = 0; y < size - 1; y++) {
                if (((local[(y * size) + x] == local[((y + 1) * size) + x]) &&
                        (local[(y * size) + x] == local[(y * size) + (x + 1)])) &&
                        (local[(y * size) + x] == local[((y + 1) * size) + (x + 1)])) {
                    result += 3;
                }
            }
        }

        /* Test 3: 1:1:3:1:1 ratio pattern in row/column */
        /* Vertical */
        for (x = 0; x < size; x++) {
            for (y = 0; y < (size - 7); y++) {
                p = 0;
                for (weight = 0; weight < 7; weight++) {
                    if (local[((y + weight) * size) + x] == '1') {
                        p += (0x40 >> weight);
                    }
                }
                if (p == 0x5d) {
                    /* Pattern found, check before and after */
                    beforeCount = 0;
                    for (b = (y - 4); b < y; b++) {
                        if (b < 0) {
                            beforeCount++;
                        } else {
                            if (local[(b * size) + x] == '0') {
                                beforeCount++;
                            } else {
                                beforeCount = 0;
                            }
                        }
                    }

                    afterCount = 0;
                    for (a = (y + 7); a <= (y + 10); a++) {
                        if (a >= size) {
                            afterCount++;
                        } else {
                            if (local[(a * size) + x] == '0') {
                                afterCount++;
                            } else {
                                afterCount = 0;
                            }
                        }
                    }

                    if ((beforeCount == 4) || (afterCount == 4)) {
                        // Pattern is preceded or followed by light area 4 modules wide
                        result += 40;
                    }
                }
            }
        }

        /* Horizontal */
        for (y = 0; y < size; y++) {
            for (x = 0; x < (size - 7); x++) {
                p = 0;
                for (weight = 0; weight < 7; weight++) {
                    if (local[(y * size) + x + weight] == '1') {
                        p += (0x40 >> weight);
                    }
                }
                if (p == 0x5d) {
                    /* Pattern found, check before and after */
                    beforeCount = 0;
                    for (b = (x - 4); b < x; b++) {
                        if (b < 0) {
                            beforeCount++;
                        } else {
                            if (local[(y * size) + b] == '0') {
                                beforeCount++;
                            } else {
                                beforeCount = 0;
                            }
                        }
                    }

                    afterCount = 0;
                    for (a = (x + 7); a <= (x + 10); a++) {
                        if (a >= size) {
                            afterCount++;
                        } else {
                            if (local[(y * size) + a] == '0') {
                                afterCount++;
                            } else {
                                afterCount = 0;
                            }
                        }
                    }

                    if ((beforeCount == 4) || (afterCount == 4)) {
                        // Pattern is preceded or followed by light area 4 modules wide
                        result += 40;
                    }
                }
            }
        }

        /* Test 4: Proportion of dark modules in entire symbol */
        dark_mods = 0;
        for (x = 0; x < size; x++) {
            for (y = 0; y < size; y++) {
                if (local[(y * size) + x] == '1') {
                    dark_mods++;
                }
            }
        }
        percentage = 100 * (dark_mods / (size * size));
        if (percentage <= 50) {
            k = ((100 - percentage) - 50) / 5;
        } else {
            k = (percentage - 50) / 5;
        }

        result += 10 * k;

        return result;
    }

    /* Adds format information to grid. */
    private static void addFormatInfo(int[] grid, int size, EccLevel ecc_level, int pattern) {

        int format = pattern;
        int seq;
        int i;

        switch (ecc_level) {
            case L:
                format += 0x08;
                break;
            case Q:
                format += 0x18;
                break;
            case H:
                format += 0x10;
                break;
        }

        seq = QR_ANNEX_C[format];

        for (i = 0; i < 6; i++) {
            grid[(i * size) + 8] += (seq >> i) & 0x01;
        }

        for (i = 0; i < 8; i++) {
            grid[(8 * size) + (size - i - 1)] += (seq >> i) & 0x01;
        }

        for (i = 0; i < 6; i++) {
            grid[(8 * size) + (5 - i)] += (seq >> (i + 9)) & 0x01;
        }

        for (i = 0; i < 7; i++) {
            grid[(((size - 7) + i) * size) + 8] += (seq >> (i + 8)) & 0x01;
        }

        grid[(7 * size) + 8] += (seq >> 6) & 0x01;
        grid[(8 * size) + 8] += (seq >> 7) & 0x01;
        grid[(8 * size) + 7] += (seq >> 8) & 0x01;
    }

    /** Adds version information. */
    private static void addVersionInfo(int[] grid, int size, int version) {
        // TODO: Zint masks with 0x41 instead of 0x01; which is correct? https://sourceforge.net/p/zint/tickets/110/
        int version_data = QR_ANNEX_D[version - 7];
        for (int i = 0; i < 6; i++) {
            grid[((size - 11) * size) + i] += (version_data >> (i * 3)) & 0x01;
            grid[((size - 10) * size) + i] += (version_data >> ((i * 3) + 1)) & 0x01;
            grid[((size - 9) * size) + i] += (version_data >> ((i * 3) + 2)) & 0x01;
            grid[(i * size) + (size - 11)] += (version_data >> (i * 3)) & 0x01;
            grid[(i * size) + (size - 10)] += (version_data >> ((i * 3) + 1)) & 0x01;
            grid[(i * size) + (size - 9)] += (version_data >> ((i * 3) + 2)) & 0x01;
        }
    }
} public class RoyalMail4State extends Symbol {

    private static final String[] ROYAL_TABLE = {
        "TTFF", "TDAF", "TDFA", "DTAF", "DTFA", "DDAA", "TADF", "TFTF", "TFDA",
        "DATF", "DADA", "DFTA", "TAFD", "TFAD", "TFFT", "DAAD", "DAFT", "DFAT",
        "ATDF", "ADTF", "ADDA", "FTTF", "FTDA", "FDTA", "ATFD", "ADAD", "ADFT",
        "FTAD", "FTFT", "FDAT", "AADD", "AFTD", "AFDT", "FATD", "FADT", "FFTT"
    };

    private static final char[] KR_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'
    };

    @Override
    protected void encode() {
        String dest;
        int i, top = 0, bottom = 0;
        int row, column;
        int index;

        content = content.toUpperCase(Locale.ENGLISH);
        if(!content.matches("[0-9A-Z]+")) {
            throw new OkapiException("Invalid characters in data");
        }
        dest = "A";

        for (i = 0; i < content.length(); i++) {
            index = positionOf(content.charAt(i), KR_SET);
            dest += ROYAL_TABLE[index];
            top += (index + 1) % 6;
            bottom += ((index / 6) + 1) % 6;
        }

        /* calculate check digit */
        row = (top % 6) - 1;
        column = (bottom % 6) - 1;
        if (row == -1) {
            row = 5;
        }
        if (column == -1) {
            column = 5;
        }
        int check = (6 * row) + column;
        dest += ROYAL_TABLE[check];
        infoLine("Check Digit: " + check);

        /* Stop character */
        dest += "F";

        infoLine("Encoding: " + dest);
        readable = "";
        pattern = new String[1];
        pattern[0] = dest;
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }

    @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 'A':
                y = 0;
                h = 5;
                break;
            case 'D':
                y = 3;
                h = 5;
                break;
            case 'F':
                y = 0;
                h = 8;
                break;
            case 'T':
                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;
    }
}
 public abstract class Symbol {

    public static enum DataType {
        ECI, GS1, HIBC
    }

    protected static final int FNC1 = -1;
    protected static final int FNC2 = -2;
    protected static final int FNC3 = -3;
    protected static final int FNC4 = -4;

    protected static final String FNC1_STRING = "\\<FNC1>";
    protected static final String FNC2_STRING = "\\<FNC2>";
    protected static final String FNC3_STRING = "\\<FNC3>";
    protected static final String FNC4_STRING = "\\<FNC4>";

    private static char[] HIBC_CHAR_TABLE = {
        '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', '-', '.', ' ', '$',
        '/', '+', '%' };

    // user-specified values and settings

    protected DataType inputDataType = DataType.ECI;
    protected boolean readerInit;
    protected int default_height = 40;
    protected int quietZoneHorizontal = 0;
    protected int quietZoneVertical = 0;
    protected int moduleWidth = 1;
    protected Font font;
    protected String fontName = "Helvetica";
    protected int fontSize = 8;
    protected HumanReadableLocation humanReadableLocation = BOTTOM;
    protected HumanReadableAlignment humanReadableAlignment = CENTER;
    protected boolean emptyContentAllowed = false;

    // internal state calculated when setContent() is called

    protected String content;
    protected int eciMode = -1;
    protected int[] inputData; // usually bytes (values 0-255), but may also contain FNC flags
    protected String readable = "";
    protected String[] pattern;
    protected int row_count = 0;
    protected int[] row_height;
    protected int symbol_height = 0;
    protected int symbol_width = 0;
    protected StringBuilder encodeInfo = new StringBuilder();
    protected List< Rectangle2D.Double > rectangles = new ArrayList<>(); // note positions do not account for quiet zones (handled in renderers)
    protected List< TextBox > texts = new ArrayList<>();                 // note positions do not account for quiet zones (handled in renderers)
    protected List< Hexagon > hexagons = new ArrayList<>();              // note positions do not account for quiet zones (handled in renderers)
    protected List< Ellipse2D.Double > target = new ArrayList<>();       // note positions do not account for quiet zones (handled in renderers)

    /**
     * <p>Sets the type of input data. This setting influences what pre-processing is done on
     * data before encoding in the symbol. For example: for <code>GS1</code> mode the AI
     * data will be used to calculate the position of 'FNC1' characters.
     *
     * <p>Valid values are:
     *
     * <ul>
     * <li><code>ECI</code> Extended Channel Interpretations (default)
     * <li><code>GS1</code> Application Identifier and data pairs in "[AI]DATA" format
     * <li><code>HIBC</code> Health Industry Bar Code number (without check digit)
     * </ul>
     *
     * @param dataType the type of input data
     */
    public void setDataType(DataType dataType) {
        if (dataType == DataType.GS1 && !gs1Supported()) {
            throw new IllegalArgumentException("This symbology type does not support GS1 data.");
        }
        inputDataType = dataType;
    }

    /**
     * Returns the type of input data in this symbol.
     *
     * @return the type of input data in this symbol
     */
    public DataType getDataType() {
        return inputDataType;
    }

    /**
     * Returns <code>true</code> if this type of symbology supports GS1 data.
     *
     * @return <code>true</code> if this type of symbology supports GS1 data
     */
    protected boolean gs1Supported() {
        return false;
    }

    /**
     * If set to <code>true</code>, the symbol is prefixed with a "Reader Initialization"
     * or "Reader Programming" instruction.
     *
     * @param readerInit whether or not to enable reader initialization
     */
    public void setReaderInit(boolean readerInit) {
        this.readerInit = readerInit;
    }

    /**
     * Returns whether or not reader initialization is enabled.
     *
     * @return whether or not reader initialization is enabled
     */
    public boolean getReaderInit() {
        return readerInit;
    }

    /**
     * Sets the default bar height for this symbol (default value is <code>40</code>).
     *
     * @param barHeight the default bar height for this symbol
     */
    public void setBarHeight(int barHeight) {
        this.default_height = barHeight;
    }

    /**
     * Returns the default bar height for this symbol.
     *
     * @return the default bar height for this symbol
     */
    public int getBarHeight() {
        return default_height;
    }

    /**
     * Sets the module width for this symbol (default value is <code>1</code>).
     *
     * @param moduleWidth the module width for this symbol
     */
    public void setModuleWidth(int moduleWidth) {
        this.moduleWidth = moduleWidth;
    }

    /**
     * Returns the module width for this symbol.
     *
     * @return the module width for this symbol
     */
    public int getModuleWidth() {
        return moduleWidth;
    }

    /**
     * Sets the horizontal quiet zone (white space) added to the left and to the right of this symbol.
     *
     * @param quietZoneHorizontal the horizontal quiet zone (white space) added to the left and to the right of this symbol
     */
    public void setQuietZoneHorizontal(int quietZoneHorizontal) {
        this.quietZoneHorizontal = quietZoneHorizontal;
    }

    /**
     * Returns the horizontal quiet zone (white space) added to the left and to the right of this symbol.
     *
     * @return the horizontal quiet zone (white space) added to the left and to the right of this symbol
     */
    public int getQuietZoneHorizontal() {
        return quietZoneHorizontal;
    }

    /**
     * Sets the vertical quiet zone (white space) added above and below this symbol.
     *
     * @param quietZoneVertical the vertical quiet zone (white space) added above and below this symbol
     */
    public void setQuietZoneVertical(int quietZoneVertical) {
        this.quietZoneVertical = quietZoneVertical;
    }

    /**
     * Returns the vertical quiet zone (white space) added above and below this symbol.
     *
     * @return the vertical quiet zone (white space) added above and below this symbol
     */
    public int getQuietZoneVertical() {
        return quietZoneVertical;
    }

    /**
     * <p>Sets the font to use to render the human-readable text. This is an alternative to setting the
     * {@link #setFontName(String) font name} and {@link #setFontSize(int) font size} separately. May
     * allow some applications to avoid the use of {@link GraphicsEnvironment#registerFont(Font)}
     * when using the {@link Java2DRenderer}.
     *
     * <p>Do not use this method in combination with {@link #setFontName(String)} or {@link #setFontSize(int)}.
     *
     * @param font the font to use to render the human-readable text
     */
    public void setFont(Font font) {
        this.font = font;
        this.fontName = font.getFontName();
        this.fontSize = font.getSize();
    }

    /**
     * Returns the font to use to render the human-readable text.
     *
     * @return the font to use to render the human-readable text
     */
    public Font getFont() {
        return font;
    }

    /**
     * <p>Sets the name of the font to use to render the human-readable text (default value is <code>Helvetica</code>).
     * The specified font name needs to be registered via {@link GraphicsEnvironment#registerFont(Font)} if you are
     * using the {@link Java2DRenderer}. In order to set the font without registering the font with the graphics
     * environment when using the {@link Java2DRenderer}, you may need to use {@link #setFont(Font)} instead.
     *
     * <p>Use this method in combination with {@link #setFontSize(int)}.
     *
     * <p>Do not use this method in combination with {@link #setFont(Font)}.
     *
     * @param fontName the name of the font to use to render the human-readable text
     */
    public void setFontName(String fontName) {
        this.fontName = Objects.requireNonNull(fontName, "font name may not be null");
        this.font = null;
    }

    /**
     * Returns the name of the font to use to render the human-readable text.
     *
     * @return the name of the font to use to render the human-readable text
     */
    public String getFontName() {
        return fontName;
    }

    /**
     * <p>Sets the size of the font to use to render the human-readable text (default value is <code>8</code>).
     *
     * <p>Use this method in combination with {@link #setFontName(String)}.
     *
     * <p>Do not use this method in combination with {@link #setFont(Font)}.
     *
     * @param fontSize the size of the font to use to render the human-readable text
     */
    public void setFontSize(int fontSize) {
        this.fontSize = fontSize;
        this.font = null;
    }

    /**
     * Returns the size of the font to use to render the human-readable text.
     *
     * @return the size of the font to use to render the human-readable text
     */
    public int getFontSize() {
        return fontSize;
    }

    /**
     * Gets the width of the encoded symbol, including the horizontal quiet zone.
     *
     * @return the width of the encoded symbol
     */
    public int getWidth() {
        return symbol_width + (2 * quietZoneHorizontal);
    }

    /**
     * Returns the height of the symbol, including the human-readable text, if any, as well as the vertical
     * quiet zone. This height is an approximation, since it is calculated without access to a font engine.
     *
     * @return the height of the symbol, including the human-readable text, if any, as well as the vertical
     *         quiet zone
     */
    public int getHeight() {
        return symbol_height + getHumanReadableHeight() + (2 * quietZoneVertical);
    }

    /**
     * Returns the height of the human-readable text, including the space between the text and other symbols.
     * This height is an approximation, since it is calculated without access to a font engine.
     *
     * @return the height of the human-readable text
     */
    public int getHumanReadableHeight() {
        if (texts.isEmpty()) {
            return 0;
        } else {
            return getTheoreticalHumanReadableHeight();
        }
    }

    /**
     * Returns the height of the human-readable text, assuming this symbol had human-readable text.
     *
     * @return the height of the human-readable text, assuming this symbol had human-readable text
     */
    protected int getTheoreticalHumanReadableHeight() {
        return (int) Math.ceil(fontSize * 1.2); // 0.2 space between bars and text
    }

    /**
     * Returns a human readable summary of the decisions made by the encoder when creating a symbol.
     *
     * @return a human readable summary of the decisions made by the encoder when creating a symbol
     */
    public String getEncodeInfo() {
        return encodeInfo.toString();
    }

    /**
     * Returns the ECI mode used by this symbol. The ECI mode is chosen automatically during encoding
     * if the symbol data type has been set to {@link DataType#ECI}. If this symbol does not use ECI,
     * this method will return <code>-1</code>.
     *
     * @return the ECI mode used by this symbol
     * @see #eciProcess()
     */
    public int getEciMode() {
        return eciMode;
    }

    /**
     * Sets the location of the human-readable text (default value is {@link HumanReadableLocation#BOTTOM}).
     *
     * @param humanReadableLocation the location of the human-readable text
     */
    public void setHumanReadableLocation(HumanReadableLocation humanReadableLocation) {
        this.humanReadableLocation = humanReadableLocation;
    }

    /**
     * Returns the location of the human-readable text.
     *
     * @return the location of the human-readable text
     */
    public HumanReadableLocation getHumanReadableLocation() {
        return humanReadableLocation;
    }

    /**
     * Sets the text alignment of the human-readable text (default value is {@link HumanReadableAlignment#CENTER}).
     *
     * @param humanReadableAlignment the text alignment of the human-readable text
     */
    public void setHumanReadableAlignment(HumanReadableAlignment humanReadableAlignment) {
        this.humanReadableAlignment = humanReadableAlignment;
    }

    /**
     * Returns the text alignment of the human-readable text.
     *
     * @return the text alignment of the human-readable text
     */
    public HumanReadableAlignment getHumanReadableAlignment() {
        return humanReadableAlignment;
    }

    /**
     * Returns render information about the rectangles in this symbol.
     *
     * @return render information about the rectangles in this symbol
     */
    public List< Rectangle2D.Double > getRectangles() {
        return rectangles;
    }

    /**
     * Returns render information about the text elements in this symbol.
     *
     * @return render information about the text elements in this symbol
     */
    public List< TextBox > getTexts() {
        return texts;
    }

    /**
     * Returns render information about the hexagons in this symbol.
     *
     * @return render information about the hexagons in this symbol
     */
    public List< Hexagon > getHexagons() {
        return hexagons;
    }

    /**
     * Returns render information about the target circles in this symbol.
     *
     * @return render information about the target circles in this symbol
     */
    public List< Ellipse2D.Double > getTarget() {
        return target;
    }

    protected static String bin2pat(CharSequence bin) {

        int len = 0;
        boolean black = true;
        StringBuilder pattern = new StringBuilder(bin.length());

        for (int i = 0; i < bin.length(); i++) {
            if (black) {
                if (bin.charAt(i) == '1') {
                    len++;
                } else {
                    black = false;
                    pattern.append((char) (len + '0'));
                    len = 1;
                }
            } else {
                if (bin.charAt(i) == '0') {
                    len++;
                } else {
                    black = true;
                    pattern.append((char) (len + '0'));
                    len = 1;
                }
            }
        }

        pattern.append((char) (len + '0'));
        return pattern.toString();
    }

    /**
     * Sets whether or not empty content is allowed. Some symbologies may be able to generate empty symbols when no data is
     * present, though this is not usually desired behavior. The default value is <code>false</code> (no empty content allowed).
     *
     * @param emptyContentAllowed whether or not empty content is allowed
     */
    public void setEmptyContentAllowed(boolean emptyContentAllowed) {
        this.emptyContentAllowed = emptyContentAllowed;
    }

    /**
     * Returns whether or not empty content is allowed.
     *
     * @return whether or not empty content is allowed
     */
    public boolean getEmptyContentAllowed() {
        return emptyContentAllowed;
    }

    /**
     * Sets the data to be encoded and triggers encoding. Input data will be assumed
     * to be of the type set by {@link #setDataType(DataType)}.
     *
     * @param data the data to encode
     * @throws OkapiException if no data or data is invalid
     */
    public void setContent(String data) {

        if (data == null) {
            data = "";
        }

        encodeInfo.setLength(0); // clear

        switch (inputDataType) {
            case GS1:
                content = Gs1.verify(data, FNC1_STRING);
                readable = data.replace('[', '(').replace(']', ')');
                break;
            case HIBC:
                content = hibcProcess(data);
                break;
            default:
                content = data;
                break;
        }

        if (content.isEmpty() && !emptyContentAllowed) {
            throw new OkapiException("No input data");
        }

        encode();
        plotSymbol();
        mergeVerticalBlocks();
    }

    /**
     * Returns the content encoded by this symbol.
     *
     * @return the content encoded by this symbol
     */
    public String getContent() {
        return content;
    }

    /**
     * Returns the human-readable text for this symbol.
     *
     * @return the human-readable text for this symbol
     */
    public String getHumanReadableText() {
        return readable;
    }

    /**
     * Chooses the ECI mode most suitable for the content of this symbol.
     */
    protected void eciProcess() {

        EciMode eci = EciMode.of(content, "ISO8859_1",    3)
                             .or(content, "ISO8859_2",    4)
                             .or(content, "ISO8859_3",    5)
                             .or(content, "ISO8859_4",    6)
                             .or(content, "ISO8859_5",    7)
                             .or(content, "ISO8859_6",    8)
                             .or(content, "ISO8859_7",    9)
                             .or(content, "ISO8859_8",    10)
                             .or(content, "ISO8859_9",    11)
                             .or(content, "ISO8859_10",   12)
                             .or(content, "ISO8859_11",   13)
                             .or(content, "ISO8859_13",   15)
                             .or(content, "ISO8859_14",   16)
                             .or(content, "ISO8859_15",   17)
                             .or(content, "ISO8859_16",   18)
                             .or(content, "Windows_1250", 21)
                             .or(content, "Windows_1251", 22)
                             .or(content, "Windows_1252", 23)
                             .or(content, "Windows_1256", 24)
                             .or(content, "SJIS",         20)
                             .or(content, "UTF8",         26);

        if (EciMode.NONE.equals(eci)) {
            throw new OkapiException("Unable to determine ECI mode.");
        }

        eciMode = eci.mode;
        inputData = toBytes(content, eci.charset);

        infoLine("ECI Mode: " + eci.mode);
        infoLine("ECI Charset: " + eci.charset.name());
    }

    protected static int[] toBytes(String s, Charset charset, int... suffix) {

        if (!charset.newEncoder().canEncode(s)) {
            return null;
        }

        byte[] fnc1 = FNC1_STRING.getBytes(charset);
        byte[] fnc2 = FNC2_STRING.getBytes(charset);
        byte[] fnc3 = FNC3_STRING.getBytes(charset);
        byte[] fnc4 = FNC4_STRING.getBytes(charset);

        byte[] bytes = s.getBytes(charset);
        int[] data = new int[bytes.length + suffix.length];

        int i = 0, j = 0;
        for (; i < bytes.length; i++, j++) {
            if (containsAt(bytes, fnc1, i)) {
                data[j] = FNC1;
                i += fnc1.length - 1;
            } else if (containsAt(bytes, fnc2, i)) {
                data[j] = FNC2;
                i += fnc1.length - 1;
            } else if (containsAt(bytes, fnc3, i)) {
                data[j] = FNC3;
                i += fnc1.length - 1;
            } else if (containsAt(bytes, fnc4, i)) {
                data[j] = FNC4;
                i += fnc1.length - 1;
            } else {
                data[j] = bytes[i] & 0xff;
            }
        }

        int k = 0;
        for (; k < suffix.length; k++) {
            data[j + k] = suffix[k];
        }

        if (j + k < i) {
            data = Arrays.copyOf(data, j + k);
        }

        return data;
    }

    protected abstract void encode();

    protected void plotSymbol() {
        int xBlock, yBlock;
        double x, y, w, h;
        boolean black;

        rectangles.clear();
        texts.clear();

        int baseY;
        if (humanReadableLocation == TOP) {
            baseY = getTheoreticalHumanReadableHeight();
        } else {
            baseY = 0;
        }

        h = 0;
        y = baseY;

        for (yBlock = 0; yBlock < row_count; yBlock++) {
            black = true;
            x = 0;
            for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) {
                char c = pattern[yBlock].charAt(xBlock);
                w = getModuleWidth(c - '0') * moduleWidth;
                if (black) {
                    if (row_height[yBlock] == -1) {
                        h = default_height;
                    } else {
                        h = row_height[yBlock];
                    }
                    if (w != 0 && h != 0) {
                        Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
                        rectangles.add(rect);
                    }
                    if (x + w > symbol_width) {
                        symbol_width = (int) Math.ceil(x + w);
                    }
                }
                black = !black;
                x += w;
            }
            if ((y - baseY + h) > symbol_height) {
                symbol_height = (int) Math.ceil(y - baseY + h);
            }
            y += h;
        }

        if (humanReadableLocation != NONE && !readable.isEmpty()) {
            double baseline;
            if (humanReadableLocation == TOP) {
                baseline = fontSize;
            } else {
                baseline = symbol_height + fontSize;
            }
            texts.add(new TextBox(0, baseline, symbol_width, readable, humanReadableAlignment));
        }
    }

    /**
     * Returns the module width to use for the specified original module width, taking into account any module width ratio
     * customizations. Intended to be overridden by subclasses that support such module width ratio customization.
     *
     * @param originalWidth the original module width
     * @return the module width to use for the specified original module width
     */
    protected double getModuleWidth(int originalWidth) {
        return originalWidth;
    }

    /**
     * Search for rectangles which have the same width and x position, and which join together vertically
     * and merge them together to reduce the number of rectangles needed to describe a symbol. This can
     * actually take a non-trivial amount of time for symbols with a large number of rectangles (like
     * large PDF417 symbols) so we exploit the fact that the rectangles are ordered by rows (and within
     * the rows that they are ordered by x position).
     */
    protected void mergeVerticalBlocks() {

        int before = rectangles.size();

        for (int i = rectangles.size() - 1; i >= 0; i--) {
            Rectangle2D.Double rect1 = rectangles.get(i);
            for (int j = i - 1; j >= 0; j--) {
                Rectangle2D.Double rect2 = rectangles.get(j);
                if (roughlyEqual(rect1.y, rect2.y + rect2.height)) {
                    // rect2 is in the segment of rectangles for the row directly above rect1
                    if (roughlyEqual(rect1.x, rect2.x) && roughlyEqual(rect1.width, rect2.width)) {
                        // we've found a match; merge the rectangles
                        rect2.height += rect1.height;
                        rectangles.remove(i);
                        break;
                    }
                    if (rect2.x < rect1.x) {
                        // we've moved past any rectangles that might be directly above rect1
                        break;
                    }
                }
            }
        }

        int after = rectangles.size();
        if (before != after) {
            infoLine("Blocks Merged: " + before + " -> " + after);
        }
    }

    /**
     * Adds the HIBC prefix and check digit to the specified data, returning the resultant data string.
     *
     * @see <a href="https://sourceforge.net/p/zint/code/ci/master/tree/backend/library.c">Corresponding Zint code</a>
     */
    private String hibcProcess(String source) {

        // HIBC 2.6 allows up to 110 characters, not including the "+" prefix or the check digit
        if (source.length() > 110) {
            throw new OkapiException("Data too long for HIBC LIC");
        }

        source = source.toUpperCase();
        if (!source.matches("[A-Z0-9-\\. \\$/+\\%]+?")) {
            throw new OkapiException("Invalid characters in input");
        }

        int counter = 41;
        for (int i = 0; i < source.length(); i++) {
            counter += positionOf(source.charAt(i), HIBC_CHAR_TABLE);
        }
        counter = counter % 43;

        char checkDigit = HIBC_CHAR_TABLE[counter];

        infoLine("HIBC Check Digit Counter: " + counter);
        infoLine("HIBC Check Digit: " + checkDigit);

        return "+" + source + checkDigit;
    }

    /**
     * Returns the intermediate coding of this bar code. Symbol types that use the test
     * infrastructure should override this method.
     *
     * @return the intermediate coding of this bar code
     */
    protected int[] getCodewords() {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns this bar code's pattern, converted into a set of corresponding codewords.
     * Useful for bar codes that encode their content as a pattern.
     *
     * @param size the number of digits in each codeword
     * @return this bar code's pattern, converted into a set of corresponding codewords
     */
    protected int[] getPatternAsCodewords(int size) {
        if (size >= 10) {
            throw new IllegalArgumentException("Pattern groups of 10 or more digits are likely to be too large to parse as integers.");
        }
        if (pattern == null || pattern.length == 0) {
            return new int[0];
        } else {
            int count = (int) Math.ceil(pattern[0].length() / (double) size);
            int[] codewords = new int[pattern.length * count];
            for (int i = 0; i < pattern.length; i++) {
                String row = pattern[i];
                for (int j = 0; j < count; j++) {
                    int substringStart = j * size;
                    int substringEnd = Math.min((j + 1) * size, row.length());
                    codewords[(i * count) + j] = Integer.parseInt(row.substring(substringStart, substringEnd));
                }
            }
            return codewords;
        }
    }

    protected void info(CharSequence s) {
        encodeInfo.append(s);
    }

    protected void infoSpace(int i) {
        encodeInfo.append(i).append(' ');
    }

    protected void infoSpace(char c) {
        encodeInfo.append(c).append(' ');
    }

    protected void infoLine(CharSequence s) {
        encodeInfo.append(s).append('\n');
    }

    protected void infoLine() {
        encodeInfo.append('\n');
    }
} public class Telepen extends Symbol {

    public static enum Mode {
        NORMAL, NUMERIC
    }

    private static final String[] TELE_TABLE = {
        "1111111111111111", "1131313111", "33313111", "1111313131",
        "3111313111", "11333131", "13133131", "111111313111", "31333111",
        "1131113131", "33113131", "1111333111", "3111113131", "1113133111",
        "1311133111", "111111113131", "3131113111", "11313331", "333331",
        "111131113111", "31113331", "1133113111", "1313113111", "1111113331",
        "31131331", "113111113111", "3311113111", "1111131331", "311111113111",
        "1113111331", "1311111331", "11111111113111", "31313311", "1131311131",
        "33311131", "1111313311", "3111311131", "11333311", "13133311",
        "111111311131", "31331131", "1131113311", "33113311", "1111331131",
        "3111113311", "1113131131", "1311131131", "111111113311", "3131111131",
        "1131131311", "33131311", "111131111131", "3111131311", "1133111131",
        "1313111131", "111111131311", "3113111311", "113111111131",
        "3311111131", "111113111311", "311111111131", "111311111311",
        "131111111311", "11111111111131", "3131311111", "11313133", "333133",
        "111131311111", "31113133", "1133311111", "1313311111", "1111113133",
        "313333", "113111311111", "3311311111", "11113333", "311111311111",
        "11131333", "13111333", "11111111311111", "31311133", "1131331111",
        "33331111", "1111311133", "3111331111", "11331133", "13131133",
        "111111331111", "3113131111", "1131111133", "33111133", "111113131111",
        "3111111133", "111311131111", "131111131111", "111111111133",
        "31311313", "113131111111", "3331111111", "1111311313", "311131111111",
        "11331313", "13131313", "11111131111111", "3133111111", "1131111313",
        "33111313", "111133111111", "3111111313", "111313111111",
        "131113111111", "111111111313", "313111111111", "1131131113",
        "33131113", "11113111111111", "3111131113", "113311111111",
        "131311111111", "111111131113", "3113111113", "11311111111111",
        "331111111111", "111113111113", "31111111111111", "111311111113",
        "131111111113"
    };

    private Mode mode = Mode.NORMAL;

    public void setMode(Mode mode) {
        this.mode = mode;
    }

    public Mode getMode() {
        return mode;
    }

    @Override
    protected void encode() {
        if (mode == Mode.NORMAL) {
            normal_mode();
        } else {
            numeric_mode();
        }
    }

    private void normal_mode() {
        int count = 0, asciicode, check_digit;
        String p = "";
        String dest;

        int l = content.length();

        if (!content.matches("[\u0000-\u007F]+")) {
            throw new OkapiException("Invalid characters in input data");
        }

        dest = TELE_TABLE['_']; // Start
        for (int i = 0; i < l; i++) {
            asciicode = content.charAt(i);
            p += TELE_TABLE[asciicode];
            count += asciicode;
        }

        check_digit = 127 - (count % 127);
        if (check_digit == 127) {
            check_digit = 0;
        }

        p += TELE_TABLE[check_digit];

        infoLine("Check Digit: " + check_digit);

        dest += p;
        dest += TELE_TABLE['z']; // Stop

        readable = content;
        pattern = new String[1];
        pattern[0] = dest;
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }

    private void numeric_mode() {
        int count = 0, check_digit;
        String p = "";
        String t;
        String dest;
        int l = content.length();
        int tl, glyph;
        char c1, c2;

        //FIXME: Ensure no extended ASCII or Unicode characters are entered
        if (!content.matches("[0-9X]+")) {
            throw new OkapiException("Invalid characters in input");
        }

        /* If input is an odd length, add a leading zero */
        if ((l & 1) == 1) {
            t = "0" + content;
            tl = l + 1;
        } else {
            t = content;
            tl = l;
        }

        dest = TELE_TABLE['_']; // Start
        for (int i = 0; i < tl; i += 2) {

            c1 = t.charAt(i);
            c2 = t.charAt(i + 1);

            /* Input nX is allowed, but Xn is not */
            if (c1 == 'X') {
                throw new OkapiException("Invalid position of X in data");
            }

            if (c2 == 'X') {
                glyph = (c1 - '0') + 17;
                count += glyph;
            } else {
                glyph = ((10 * (c1 - '0')) + (c2 - '0')) + 27;
                count += glyph;
            }

            p += TELE_TABLE[glyph];
        }

        check_digit = 127 - (count % 127);
        if (check_digit == 127) {
            check_digit = 0;
        }

        p += TELE_TABLE[check_digit];

        infoLine("Check Digit: " + check_digit);

        dest += p;
        dest += TELE_TABLE['z']; // Stop
        readable = content;
        pattern = new String[1];
        pattern[0] = dest;
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }
}
 public class Upc extends Symbol {

    public static enum Mode {
        UPCA, UPCE
    };

    private static final String[] SET_AC = {
        "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", "3112"
    };

    private static final String[] SET_B = {
        "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", "2113"
    };

    /* Number set for UPC-E symbol (EN Table 4) */
    private static final String[] UPC_PARITY_0 = {
        "BBBAAA", "BBABAA", "BBAABA", "BBAAAB", "BABBAA", "BAABBA", "BAAABB",
        "BABABA", "BABAAB", "BAABAB"
    };

    /* Not covered by BS EN 797 */
    private static final String[] UPC_PARITY_1 = {
        "AAABBB", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA",
        "ABABAB", "ABABBA", "ABBABA"
    };

    private Mode mode = Mode.UPCA;
    private boolean showCheckDigit = true;
    private int guardPatternExtraHeight = 5;
    private boolean linkageFlag;
    private EanUpcAddOn addOn;

    /** Creates a new instance. */
    public Upc() {
        this.humanReadableAlignment = HumanReadableAlignment.JUSTIFY;
    }

    /**
     * Sets the UPC mode (UPC-A or UPC-E). The default is UPC-A.
     *
     * @param mode the UPC mode (UPC-A or UPC-E)
     */
    public void setMode(Mode mode) {
        this.mode = mode;
    }

    /**
     * Returns the UPC mode (UPC-A or UPC-E).
     *
     * @return the UPC mode (UPC-A or UPC-E)
     */
    public Mode getMode() {
        return mode;
    }

    /**
     * Sets whether or not to show the check digit in the human-readable text.
     *
     * @param showCheckDigit whether or not to show the check digit in the human-readable text
     */
    public void setShowCheckDigit(boolean showCheckDigit) {
        this.showCheckDigit = showCheckDigit;
    }

    /**
     * Returns whether or not to show the check digit in the human-readable text.
     *
     * @return whether or not to show the check digit in the human-readable text
     */
    public boolean getShowCheckDigit() {
        return showCheckDigit;
    }

    /**
     * Sets the extra height used for the guard patterns. The default value is <code>5</code>.
     *
     * @param guardPatternExtraHeight the extra height used for the guard patterns
     */
    public void setGuardPatternExtraHeight(int guardPatternExtraHeight) {
        this.guardPatternExtraHeight = guardPatternExtraHeight;
    }

    /**
     * Returns the extra height used for the guard patterns.
     *
     * @return the extra height used for the guard patterns
     */
    public int getGuardPatternExtraHeight() {
        return guardPatternExtraHeight;
    }

    /**
     * Sets the linkage flag. If set to <code>true</code>, this symbol is part of a composite symbol.
     *
     * @param linkageFlag the linkage flag
     */
    protected void setLinkageFlag(boolean linkageFlag) {
        this.linkageFlag = linkageFlag;
    }

    @Override
    protected void encode() {

        separateContent();

        if (content.isEmpty()) {
            throw new OkapiException("Missing UPC data");
        }

        if (mode == Mode.UPCA) {
            upca();
        } else {
            upce();
        }
    }

    private void separateContent() {
        int splitPoint = content.indexOf('+');
        if (splitPoint == -1) {
            // there is no add-on data
            addOn = null;
        } else if (splitPoint == content.length() - 1) {
            // we found the add-on separator, but no add-on data
            throw new OkapiException("Invalid add-on data");
        } else {
            // there is a '+' in the input data, use an add-on EAN2 or EAN5
            addOn = new EanUpcAddOn();
            addOn.font = this.font;
            addOn.fontName = this.fontName;
            addOn.fontSize = this.fontSize;
            addOn.humanReadableLocation = (this.humanReadableLocation == NONE ? NONE : TOP);
            addOn.moduleWidth = this.moduleWidth;
            addOn.default_height = this.default_height + this.guardPatternExtraHeight - 8;
            addOn.setContent(content.substring(splitPoint + 1));
            content = content.substring(0, splitPoint);
        }
    }

    private void upca() {

        content = validateAndPad(content, 11);

        char check = calcDigit(content);
        infoLine("Check Digit: " + check);

        String hrt = content + check;

        StringBuilder dest = new StringBuilder("111");
        for (int i = 0; i < 12; i++) {
            if (i == 6) {
                dest.append("11111");
            }
            dest.append(SET_AC[hrt.charAt(i) - '0']);
        }
        dest.append("111");

        readable = hrt;
        pattern = new String[] { dest.toString() };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    private void upce() {

        content = validateAndPad(content, 7);

        String expanded = expandToEquivalentUpcA(content, true);
        infoLine("UPC-A Equivalent: " + expanded);

        char check = calcDigit(expanded);
        infoLine("Check Digit: " + check);

        String hrt = content + check;

        int numberSystem = getNumberSystem(content);
        String[] parityArray = (numberSystem == 1 ? UPC_PARITY_1 : UPC_PARITY_0);
        String parity = parityArray[check - '0'];

        StringBuilder dest = new StringBuilder("111");
        for (int i = 0; i < 6; i++) {
            if (parity.charAt(i) == 'A') {
                dest.append(SET_AC[content.charAt(i + 1) - '0']);
            } else { // B
                dest.append(SET_B[content.charAt(i + 1) - '0']);
            }
        }
        dest.append("111111");

        readable = hrt;
        pattern = new String[] { dest.toString() };
        row_count = 1;
        row_height = new int[] { -1 };
    }

    /**
     * Expands the zero-compressed UPC-E code to make a UPC-A equivalent (EN Table 5).
     *
     * @param content the UPC-E code to expand
     * @param validate whether or not to validate the input
     * @return the UPC-A equivalent of the specified UPC-E code
     */
    protected String expandToEquivalentUpcA(String content, boolean validate) {

        char[] upce = content.toCharArray();
        char[] upca = new char[11];
        Arrays.fill(upca, '0');
        upca[0] = upce[0];
        upca[1] = upce[1];
        upca[2] = upce[2];

        char emode = upce[6];

        switch (emode) {
            case '0':
            case '1':
            case '2':
                upca[3] = emode;
                upca[8] = upce[3];
                upca[9] = upce[4];
                upca[10] = upce[5];
                break;
            case '3':
                upca[3] = upce[3];
                upca[9] = upce[4];
                upca[10] = upce[5];
                if (validate && (upce[3] == '0' || upce[3] == '1' || upce[3] == '2')) {
                    /* Note 1 - "X3 shall not be equal to 0, 1 or 2" */
                    throw new OkapiException("Invalid UPC-E data");
                }
                break;
            case '4':
                upca[3] = upce[3];
                upca[4] = upce[4];
                upca[10] = upce[5];
                if (validate && upce[4] == '0') {
                    /* Note 2 - "X4 shall not be equal to 0" */
                    throw new OkapiException("Invalid UPC-E data");
                }
                break;
            default:
                upca[3] = upce[3];
                upca[4] = upce[4];
                upca[5] = upce[5];
                upca[10] = emode;
                if (validate && upce[5] == '0') {
                    /* Note 3 - "X5 shall not be equal to 0" */
                    throw new OkapiException("Invalid UPC-E data");
                }
                break;
        }

        return new String(upca);
    }

    /** Two number systems can be used: system 0 and system 1. */
    private static int getNumberSystem(String content) {
        switch (content.charAt(0)) {
            case '0':
                return 0;
            case '1':
                return 1;
            default:
                throw new OkapiException("Invalid input data");
        }
    }

    @Override
    protected void plotSymbol() {

        int xBlock;
        int x, y, w, h;
        boolean black = true;
        int compositeOffset = (linkageFlag ? 6 : 0); // space for composite separator above
        int hrtOffset = (humanReadableLocation == TOP ? getTheoreticalHumanReadableHeight() : 0); // space for HRT above

        rectangles.clear();
        texts.clear();
        x = 0;

        /* Draw the bars in the symbology */
        for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {

            w = pattern[0].charAt(xBlock) - '0';

            if (black) {
                y = 0;
                h = default_height;
                /* Add extension to guide bars */
                if (mode == Mode.UPCA) {
                    if (x < 10 || x > 84 || (x > 45 && x < 49)) {
                        h += guardPatternExtraHeight;
                    }
                    if (linkageFlag && (x == 0 || x == 94)) {
                        h += 2;
                        y -= 2;
                    }
                } else {
                    if (x < 4 || x > 45) {
                        h += guardPatternExtraHeight;
                    }
                    if (linkageFlag && (x == 0 || x == 50)) {
                        h += 2;
                        y -= 2;
                    }
                }
                Rectangle2D.Double rect = new Rectangle2D.Double(scale(x), y + compositeOffset + hrtOffset, scale(w), h);
                rectangles.add(rect);
                symbol_width = Math.max(symbol_width, (int) rect.getMaxX());
                symbol_height = Math.max(symbol_height, (int) rect.getHeight());
            }

            black = !black;
            x += w;
        }

        /* Add separator for composite symbology, if necessary */
        if (linkageFlag) {
            if (mode == Mode.UPCA) {
                rectangles.add(new Rectangle2D.Double(scale(0),  0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(94), 0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(95), 2, scale(1), 2));
            } else { // UPCE
                rectangles.add(new Rectangle2D.Double(scale(0),  0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(50), 0, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                rectangles.add(new Rectangle2D.Double(scale(51), 2, scale(1), 2));
            }
            symbol_height += 4;
        }

        /* Now add the text */
        if (humanReadableLocation == BOTTOM) {
            symbol_height -= guardPatternExtraHeight;
            double baseline = symbol_height + fontSize;
            if (mode == Mode.UPCA) {
                texts.add(new TextBox(scale(-9), baseline, scale(4), readable.substring(0, 1), HumanReadableAlignment.RIGHT));
                texts.add(new TextBox(scale(12), baseline, scale(32), readable.substring(1, 6), humanReadableAlignment));
                texts.add(new TextBox(scale(51), baseline, scale(32), readable.substring(6, 11), humanReadableAlignment));
                if (showCheckDigit) {
                    texts.add(new TextBox(scale(97), baseline, scale(4), readable.substring(11, 12), HumanReadableAlignment.LEFT));
                }
            } else { // UPCE
                texts.add(new TextBox(scale(-9), baseline, scale(4), readable.substring(0, 1), HumanReadableAlignment.RIGHT));
                texts.add(new TextBox(scale(5), baseline, scale(39), readable.substring(1, 7), humanReadableAlignment));
                if (showCheckDigit) {
                    texts.add(new TextBox(scale(53), baseline, scale(4), readable.substring(7, 8), HumanReadableAlignment.LEFT));
                }
            }
        } else if (humanReadableLocation == TOP) {
            double baseline = fontSize;
            int width = (mode == Mode.UPCA ? 94 : 50);
            texts.add(new TextBox(scale(0), baseline, scale(width), readable, humanReadableAlignment));
        }

        /* Now add the add-on symbol, if necessary */
        if (addOn != null) {
            int gap = 9;
            int baseX = symbol_width + scale(gap);
            Rectangle2D.Double r1 = rectangles.get(0);
            Rectangle2D.Double ar1 = addOn.rectangles.get(0);
            int baseY = (int) (r1.y + r1.getHeight() - ar1.y - ar1.getHeight());
            for (TextBox t : addOn.getTexts()) {
                texts.add(new TextBox(baseX + t.x, baseY + t.y, t.width, t.text, t.alignment));
            }
            for (Rectangle2D.Double r : addOn.getRectangles()) {
                rectangles.add(new Rectangle2D.Double(baseX + r.x, baseY + r.y, r.width, r.height));
            }
            symbol_width += scale(gap) + addOn.symbol_width;
            pattern[0] = pattern[0] + gap + addOn.pattern[0];
        }
    }

    /** Scales the specified width or x-dimension according to the current module width. */
    private int scale(int w) {
        return moduleWidth * w;
    }
} public class UspsOneCode extends Symbol {

    /* The following lookup tables were generated using the code in Appendix C */

    /** Appendix D Table 1 - 5 of 13 characters */
    private static final int[] APPX_D_I = {
        0x001F, 0x1F00, 0x002F, 0x1E80, 0x0037, 0x1D80, 0x003B, 0x1B80, 0x003D, 0x1780,
        0x003E, 0x0F80, 0x004F, 0x1E40, 0x0057, 0x1D40, 0x005B, 0x1B40, 0x005D, 0x1740,
        0x005E, 0x0F40, 0x0067, 0x1CC0, 0x006B, 0x1AC0, 0x006D, 0x16C0, 0x006E, 0x0EC0,
        0x0073, 0x19C0, 0x0075, 0x15C0, 0x0076, 0x0DC0, 0x0079, 0x13C0, 0x007A, 0x0BC0,
        0x007C, 0x07C0, 0x008F, 0x1E20, 0x0097, 0x1D20, 0x009B, 0x1B20, 0x009D, 0x1720,
        0x009E, 0x0F20, 0x00A7, 0x1CA0, 0x00AB, 0x1AA0, 0x00AD, 0x16A0, 0x00AE, 0x0EA0,
        0x00B3, 0x19A0, 0x00B5, 0x15A0, 0x00B6, 0x0DA0, 0x00B9, 0x13A0, 0x00BA, 0x0BA0,
        0x00BC, 0x07A0, 0x00C7, 0x1C60, 0x00CB, 0x1A60, 0x00CD, 0x1660, 0x00CE, 0x0E60,
        0x00D3, 0x1960, 0x00D5, 0x1560, 0x00D6, 0x0D60, 0x00D9, 0x1360, 0x00DA, 0x0B60,
        0x00DC, 0x0760, 0x00E3, 0x18E0, 0x00E5, 0x14E0, 0x00E6, 0x0CE0, 0x00E9, 0x12E0,
        0x00EA, 0x0AE0, 0x00EC, 0x06E0, 0x00F1, 0x11E0, 0x00F2, 0x09E0, 0x00F4, 0x05E0,
        0x00F8, 0x03E0, 0x010F, 0x1E10, 0x0117, 0x1D10, 0x011B, 0x1B10, 0x011D, 0x1710,
        0x011E, 0x0F10, 0x0127, 0x1C90, 0x012B, 0x1A90, 0x012D, 0x1690, 0x012E, 0x0E90,
        0x0133, 0x1990, 0x0135, 0x1590, 0x0136, 0x0D90, 0x0139, 0x1390, 0x013A, 0x0B90,
        0x013C, 0x0790, 0x0147, 0x1C50, 0x014B, 0x1A50, 0x014D, 0x1650, 0x014E, 0x0E50,
        0x0153, 0x1950, 0x0155, 0x1550, 0x0156, 0x0D50, 0x0159, 0x1350, 0x015A, 0x0B50,
        0x015C, 0x0750, 0x0163, 0x18D0, 0x0165, 0x14D0, 0x0166, 0x0CD0, 0x0169, 0x12D0,
        0x016A, 0x0AD0, 0x016C, 0x06D0, 0x0171, 0x11D0, 0x0172, 0x09D0, 0x0174, 0x05D0,
        0x0178, 0x03D0, 0x0187, 0x1C30, 0x018B, 0x1A30, 0x018D, 0x1630, 0x018E, 0x0E30,
        0x0193, 0x1930, 0x0195, 0x1530, 0x0196, 0x0D30, 0x0199, 0x1330, 0x019A, 0x0B30,
        0x019C, 0x0730, 0x01A3, 0x18B0, 0x01A5, 0x14B0, 0x01A6, 0x0CB0, 0x01A9, 0x12B0,
        0x01AA, 0x0AB0, 0x01AC, 0x06B0, 0x01B1, 0x11B0, 0x01B2, 0x09B0, 0x01B4, 0x05B0,
        0x01B8, 0x03B0, 0x01C3, 0x1870, 0x01C5, 0x1470, 0x01C6, 0x0C70, 0x01C9, 0x1270,
        0x01CA, 0x0A70, 0x01CC, 0x0670, 0x01D1, 0x1170, 0x01D2, 0x0970, 0x01D4, 0x0570,
        0x01D8, 0x0370, 0x01E1, 0x10F0, 0x01E2, 0x08F0, 0x01E4, 0x04F0, 0x01E8, 0x02F0,
        0x020F, 0x1E08, 0x0217, 0x1D08, 0x021B, 0x1B08, 0x021D, 0x1708, 0x021E, 0x0F08,
        0x0227, 0x1C88, 0x022B, 0x1A88, 0x022D, 0x1688, 0x022E, 0x0E88, 0x0233, 0x1988,
        0x0235, 0x1588, 0x0236, 0x0D88, 0x0239, 0x1388, 0x023A, 0x0B88, 0x023C, 0x0788,
        0x0247, 0x1C48, 0x024B, 0x1A48, 0x024D, 0x1648, 0x024E, 0x0E48, 0x0253, 0x1948,
        0x0255, 0x1548, 0x0256, 0x0D48, 0x0259, 0x1348, 0x025A, 0x0B48, 0x025C, 0x0748,
        0x0263, 0x18C8, 0x0265, 0x14C8, 0x0266, 0x0CC8, 0x0269, 0x12C8, 0x026A, 0x0AC8,
        0x026C, 0x06C8, 0x0271, 0x11C8, 0x0272, 0x09C8, 0x0274, 0x05C8, 0x0278, 0x03C8,
        0x0287, 0x1C28, 0x028B, 0x1A28, 0x028D, 0x1628, 0x028E, 0x0E28, 0x0293, 0x1928,
        0x0295, 0x1528, 0x0296, 0x0D28, 0x0299, 0x1328, 0x029A, 0x0B28, 0x029C, 0x0728,
        0x02A3, 0x18A8, 0x02A5, 0x14A8, 0x02A6, 0x0CA8, 0x02A9, 0x12A8, 0x02AA, 0x0AA8,
        0x02AC, 0x06A8, 0x02B1, 0x11A8, 0x02B2, 0x09A8, 0x02B4, 0x05A8, 0x02B8, 0x03A8,
        0x02C3, 0x1868, 0x02C5, 0x1468, 0x02C6, 0x0C68, 0x02C9, 0x1268, 0x02CA, 0x0A68,
        0x02CC, 0x0668, 0x02D1, 0x1168, 0x02D2, 0x0968, 0x02D4, 0x0568, 0x02D8, 0x0368,
        0x02E1, 0x10E8, 0x02E2, 0x08E8, 0x02E4, 0x04E8, 0x0307, 0x1C18, 0x030B, 0x1A18,
        0x030D, 0x1618, 0x030E, 0x0E18, 0x0313, 0x1918, 0x0315, 0x1518, 0x0316, 0x0D18,
        0x0319, 0x1318, 0x031A, 0x0B18, 0x031C, 0x0718, 0x0323, 0x1898, 0x0325, 0x1498,
        0x0326, 0x0C98, 0x0329, 0x1298, 0x032A, 0x0A98, 0x032C, 0x0698, 0x0331, 0x1198,
        0x0332, 0x0998, 0x0334, 0x0598, 0x0338, 0x0398, 0x0343, 0x1858, 0x0345, 0x1458,
        0x0346, 0x0C58, 0x0349, 0x1258, 0x034A, 0x0A58, 0x034C, 0x0658, 0x0351, 0x1158,
        0x0352, 0x0958, 0x0354, 0x0558, 0x0361, 0x10D8, 0x0362, 0x08D8, 0x0364, 0x04D8,
        0x0383, 0x1838, 0x0385, 0x1438, 0x0386, 0x0C38, 0x0389, 0x1238, 0x038A, 0x0A38,
        0x038C, 0x0638, 0x0391, 0x1138, 0x0392, 0x0938, 0x0394, 0x0538, 0x03A1, 0x10B8,
        0x03A2, 0x08B8, 0x03A4, 0x04B8, 0x03C1, 0x1078, 0x03C2, 0x0878, 0x03C4, 0x0478,
        0x040F, 0x1E04, 0x0417, 0x1D04, 0x041B, 0x1B04, 0x041D, 0x1704, 0x041E, 0x0F04,
        0x0427, 0x1C84, 0x042B, 0x1A84, 0x042D, 0x1684, 0x042E, 0x0E84, 0x0433, 0x1984,
        0x0435, 0x1584, 0x0436, 0x0D84, 0x0439, 0x1384, 0x043A, 0x0B84, 0x043C, 0x0784,
        0x0447, 0x1C44, 0x044B, 0x1A44, 0x044D, 0x1644, 0x044E, 0x0E44, 0x0453, 0x1944,
        0x0455, 0x1544, 0x0456, 0x0D44, 0x0459, 0x1344, 0x045A, 0x0B44, 0x045C, 0x0744,
        0x0463, 0x18C4, 0x0465, 0x14C4, 0x0466, 0x0CC4, 0x0469, 0x12C4, 0x046A, 0x0AC4,
        0x046C, 0x06C4, 0x0471, 0x11C4, 0x0472, 0x09C4, 0x0474, 0x05C4, 0x0487, 0x1C24,
        0x048B, 0x1A24, 0x048D, 0x1624, 0x048E, 0x0E24, 0x0493, 0x1924, 0x0495, 0x1524,
        0x0496, 0x0D24, 0x0499, 0x1324, 0x049A, 0x0B24, 0x049C, 0x0724, 0x04A3, 0x18A4,
        0x04A5, 0x14A4, 0x04A6, 0x0CA4, 0x04A9, 0x12A4, 0x04AA, 0x0AA4, 0x04AC, 0x06A4,
        0x04B1, 0x11A4, 0x04B2, 0x09A4, 0x04B4, 0x05A4, 0x04C3, 0x1864, 0x04C5, 0x1464,
        0x04C6, 0x0C64, 0x04C9, 0x1264, 0x04CA, 0x0A64, 0x04CC, 0x0664, 0x04D1, 0x1164,
        0x04D2, 0x0964, 0x04D4, 0x0564, 0x04E1, 0x10E4, 0x04E2, 0x08E4, 0x0507, 0x1C14,
        0x050B, 0x1A14, 0x050D, 0x1614, 0x050E, 0x0E14, 0x0513, 0x1914, 0x0515, 0x1514,
        0x0516, 0x0D14, 0x0519, 0x1314, 0x051A, 0x0B14, 0x051C, 0x0714, 0x0523, 0x1894,
        0x0525, 0x1494, 0x0526, 0x0C94, 0x0529, 0x1294, 0x052A, 0x0A94, 0x052C, 0x0694,
        0x0531, 0x1194, 0x0532, 0x0994, 0x0534, 0x0594, 0x0543, 0x1854, 0x0545, 0x1454,
        0x0546, 0x0C54, 0x0549, 0x1254, 0x054A, 0x0A54, 0x054C, 0x0654, 0x0551, 0x1154,
        0x0552, 0x0954, 0x0561, 0x10D4, 0x0562, 0x08D4, 0x0583, 0x1834, 0x0585, 0x1434,
        0x0586, 0x0C34, 0x0589, 0x1234, 0x058A, 0x0A34, 0x058C, 0x0634, 0x0591, 0x1134,
        0x0592, 0x0934, 0x05A1, 0x10B4, 0x05A2, 0x08B4, 0x05C1, 0x1074, 0x05C2, 0x0874,
        0x0607, 0x1C0C, 0x060B, 0x1A0C, 0x060D, 0x160C, 0x060E, 0x0E0C, 0x0613, 0x190C,
        0x0615, 0x150C, 0x0616, 0x0D0C, 0x0619, 0x130C, 0x061A, 0x0B0C, 0x061C, 0x070C,
        0x0623, 0x188C, 0x0625, 0x148C, 0x0626, 0x0C8C, 0x0629, 0x128C, 0x062A, 0x0A8C,
        0x062C, 0x068C, 0x0631, 0x118C, 0x0632, 0x098C, 0x0643, 0x184C, 0x0645, 0x144C,
        0x0646, 0x0C4C, 0x0649, 0x124C, 0x064A, 0x0A4C, 0x0651, 0x114C, 0x0652, 0x094C,
        0x0661, 0x10CC, 0x0662, 0x08CC, 0x0683, 0x182C, 0x0685, 0x142C, 0x0686, 0x0C2C,
        0x0689, 0x122C, 0x068A, 0x0A2C, 0x0691, 0x112C, 0x0692, 0x092C, 0x06A1, 0x10AC,
        0x06A2, 0x08AC, 0x06C1, 0x106C, 0x06C2, 0x086C, 0x0703, 0x181C, 0x0705, 0x141C,
        0x0706, 0x0C1C, 0x0709, 0x121C, 0x070A, 0x0A1C, 0x0711, 0x111C, 0x0712, 0x091C,
        0x0721, 0x109C, 0x0722, 0x089C, 0x0741, 0x105C, 0x0742, 0x085C, 0x0781, 0x103C,
        0x0782, 0x083C, 0x080F, 0x1E02, 0x0817, 0x1D02, 0x081B, 0x1B02, 0x081D, 0x1702,
        0x081E, 0x0F02, 0x0827, 0x1C82, 0x082B, 0x1A82, 0x082D, 0x1682, 0x082E, 0x0E82,
        0x0833, 0x1982, 0x0835, 0x1582, 0x0836, 0x0D82, 0x0839, 0x1382, 0x083A, 0x0B82,
        0x0847, 0x1C42, 0x084B, 0x1A42, 0x084D, 0x1642, 0x084E, 0x0E42, 0x0853, 0x1942,
        0x0855, 0x1542, 0x0856, 0x0D42, 0x0859, 0x1342, 0x085A, 0x0B42, 0x0863, 0x18C2,
        0x0865, 0x14C2, 0x0866, 0x0CC2, 0x0869, 0x12C2, 0x086A, 0x0AC2, 0x0871, 0x11C2,
        0x0872, 0x09C2, 0x0887, 0x1C22, 0x088B, 0x1A22, 0x088D, 0x1622, 0x088E, 0x0E22,
        0x0893, 0x1922, 0x0895, 0x1522, 0x0896, 0x0D22, 0x0899, 0x1322, 0x089A, 0x0B22,
        0x08A3, 0x18A2, 0x08A5, 0x14A2, 0x08A6, 0x0CA2, 0x08A9, 0x12A2, 0x08AA, 0x0AA2,
        0x08B1, 0x11A2, 0x08B2, 0x09A2, 0x08C3, 0x1862, 0x08C5, 0x1462, 0x08C6, 0x0C62,
        0x08C9, 0x1262, 0x08CA, 0x0A62, 0x08D1, 0x1162, 0x08D2, 0x0962, 0x08E1, 0x10E2,
        0x0907, 0x1C12, 0x090B, 0x1A12, 0x090D, 0x1612, 0x090E, 0x0E12, 0x0913, 0x1912,
        0x0915, 0x1512, 0x0916, 0x0D12, 0x0919, 0x1312, 0x091A, 0x0B12, 0x0923, 0x1892,
        0x0925, 0x1492, 0x0926, 0x0C92, 0x0929, 0x1292, 0x092A, 0x0A92, 0x0931, 0x1192,
        0x0932, 0x0992, 0x0943, 0x1852, 0x0945, 0x1452, 0x0946, 0x0C52, 0x0949, 0x1252,
        0x094A, 0x0A52, 0x0951, 0x1152, 0x0961, 0x10D2, 0x0983, 0x1832, 0x0985, 0x1432,
        0x0986, 0x0C32, 0x0989, 0x1232, 0x098A, 0x0A32, 0x0991, 0x1132, 0x09A1, 0x10B2,
        0x09C1, 0x1072, 0x0A07, 0x1C0A, 0x0A0B, 0x1A0A, 0x0A0D, 0x160A, 0x0A0E, 0x0E0A,
        0x0A13, 0x190A, 0x0A15, 0x150A, 0x0A16, 0x0D0A, 0x0A19, 0x130A, 0x0A1A, 0x0B0A,
        0x0A23, 0x188A, 0x0A25, 0x148A, 0x0A26, 0x0C8A, 0x0A29, 0x128A, 0x0A2A, 0x0A8A,
        0x0A31, 0x118A, 0x0A43, 0x184A, 0x0A45, 0x144A, 0x0A46, 0x0C4A, 0x0A49, 0x124A,
        0x0A51, 0x114A, 0x0A61, 0x10CA, 0x0A83, 0x182A, 0x0A85, 0x142A, 0x0A86, 0x0C2A,
        0x0A89, 0x122A, 0x0A91, 0x112A, 0x0AA1, 0x10AA, 0x0AC1, 0x106A, 0x0B03, 0x181A,
        0x0B05, 0x141A, 0x0B06, 0x0C1A, 0x0B09, 0x121A, 0x0B11, 0x111A, 0x0B21, 0x109A,
        0x0B41, 0x105A, 0x0B81, 0x103A, 0x0C07, 0x1C06, 0x0C0B, 0x1A06, 0x0C0D, 0x1606,
        0x0C0E, 0x0E06, 0x0C13, 0x1906, 0x0C15, 0x1506, 0x0C16, 0x0D06, 0x0C19, 0x1306,
        0x0C23, 0x1886, 0x0C25, 0x1486, 0x0C26, 0x0C86, 0x0C29, 0x1286, 0x0C31, 0x1186,
        0x0C43, 0x1846, 0x0C45, 0x1446, 0x0C49, 0x1246, 0x0C51, 0x1146, 0x0C61, 0x10C6,
        0x0C83, 0x1826, 0x0C85, 0x1426, 0x0C89, 0x1226, 0x0C91, 0x1126, 0x0CA1, 0x10A6,
        0x0CC1, 0x1066, 0x0D03, 0x1816, 0x0D05, 0x1416, 0x0D09, 0x1216, 0x0D11, 0x1116,
        0x0D21, 0x1096, 0x0D41, 0x1056, 0x0D81, 0x1036, 0x0E03, 0x180E, 0x0E05, 0x140E,
        0x0E09, 0x120E, 0x0E11, 0x110E, 0x0E21, 0x108E, 0x0E41, 0x104E, 0x0E81, 0x102E,
        0x0F01, 0x101E, 0x100F, 0x1E01, 0x1017, 0x1D01, 0x101B, 0x1B01, 0x101D, 0x1701,
        0x1027, 0x1C81, 0x102B, 0x1A81, 0x102D, 0x1681, 0x1033, 0x1981, 0x1035, 0x1581,
        0x1039, 0x1381, 0x1047, 0x1C41, 0x104B, 0x1A41, 0x104D, 0x1641, 0x1053, 0x1941,
        0x1055, 0x1541, 0x1059, 0x1341, 0x1063, 0x18C1, 0x1065, 0x14C1, 0x1069, 0x12C1,
        0x1071, 0x11C1, 0x1087, 0x1C21, 0x108B, 0x1A21, 0x108D, 0x1621, 0x1093, 0x1921,
        0x1095, 0x1521, 0x1099, 0x1321, 0x10A3, 0x18A1, 0x10A5, 0x14A1, 0x10A9, 0x12A1,
        0x10B1, 0x11A1, 0x10C3, 0x1861, 0x10C5, 0x1461, 0x10C9, 0x1261, 0x10D1, 0x1161,
        0x1107, 0x1C11, 0x110B, 0x1A11, 0x110D, 0x1611, 0x1113, 0x1911, 0x1115, 0x1511,
        0x1119, 0x1311, 0x1123, 0x1891, 0x1125, 0x1491, 0x1129, 0x1291, 0x1131, 0x1191,
        0x1143, 0x1851, 0x1145, 0x1451, 0x1149, 0x1251, 0x1183, 0x1831, 0x1185, 0x1431,
        0x1189, 0x1231, 0x1207, 0x1C09, 0x120B, 0x1A09, 0x120D, 0x1609, 0x1213, 0x1909,
        0x1215, 0x1509, 0x1219, 0x1309, 0x1223, 0x1889, 0x1225, 0x1489, 0x1229, 0x1289,
        0x1243, 0x1849, 0x1245, 0x1449, 0x1283, 0x1829, 0x1285, 0x1429, 0x1303, 0x1819,
        0x1305, 0x1419, 0x1407, 0x1C05, 0x140B, 0x1A05, 0x140D, 0x1605, 0x1413, 0x1905,
        0x1415, 0x1505, 0x1423, 0x1885, 0x1425, 0x1485, 0x1443, 0x1845, 0x1483, 0x1825,
        0x1503, 0x1815, 0x1603, 0x180D, 0x1807, 0x1C03, 0x180B, 0x1A03, 0x1813, 0x1903,
        0x1823, 0x1883, 0x1843, 0x1445, 0x1249, 0x1151, 0x10E1, 0x0C46, 0x0A4A, 0x0952,
        0x08E2, 0x064C, 0x0554, 0x04E4, 0x0358, 0x02E8, 0x01F0
    };

    /** Appendix D Table II - 2 of 13 characters */
    private static final int[] APPX_D_II = {
        0x0003, 0x1800, 0x0005, 0x1400, 0x0006, 0x0C00, 0x0009, 0x1200, 0x000A, 0x0A00,
        0x000C, 0x0600, 0x0011, 0x1100, 0x0012, 0x0900, 0x0014, 0x0500, 0x0018, 0x0300,
        0x0021, 0x1080, 0x0022, 0x0880, 0x0024, 0x0480, 0x0028, 0x0280, 0x0030, 0x0180,
        0x0041, 0x1040, 0x0042, 0x0840, 0x0044, 0x0440, 0x0048, 0x0240, 0x0050, 0x0140,
        0x0060, 0x00C0, 0x0081, 0x1020, 0x0082, 0x0820, 0x0084, 0x0420, 0x0088, 0x0220,
        0x0090, 0x0120, 0x0101, 0x1010, 0x0102, 0x0810, 0x0104, 0x0410, 0x0108, 0x0210,
        0x0201, 0x1008, 0x0202, 0x0808, 0x0204, 0x0408, 0x0401, 0x1004, 0x0402, 0x0804,
        0x0801, 0x1002, 0x1001, 0x0802, 0x0404, 0x0208, 0x0110, 0x00A0
    };

    /** Appendix D Table IV - Bar-to-Character Mapping (reverse lookup) */
    private static final int[] APPX_D_IV = {
        67, 6, 78, 16, 86, 95, 34, 40, 45, 113, 117, 121, 62, 87, 18, 104, 41, 76, 57, 119, 115, 72, 97,
        2, 127, 26, 105, 35, 122, 52, 114, 7, 24, 82, 68, 63, 94, 44, 77, 112, 70, 100, 39, 30, 107,
        15, 125, 85, 10, 65, 54, 88, 20, 106, 46, 66, 8, 116, 29, 61, 99, 80, 90, 37, 123, 51, 25, 84,
        129, 56, 4, 109, 96, 28, 36, 47, 11, 71, 33, 102, 21, 9, 17, 49, 124, 79, 64, 91, 42, 69, 53,
        60, 14, 1, 27, 103, 126, 75, 89, 50, 120, 19, 32, 110, 92, 111, 130, 59, 31, 12, 81, 43, 55,
        5, 74, 22, 101, 128, 58, 118, 48, 108, 38, 98, 93, 23, 83, 13, 73, 3
    };

    private double moduleWidthRatio;
    private double shortHeightPercentage;
    private double longHeightPercentage;

    public UspsOneCode() {
        this.default_height = 8;
        this.humanReadableLocation = HumanReadableLocation.NONE;
        this.humanReadableAlignment = HumanReadableAlignment.LEFT; // spec section 2.4.2
        this.moduleWidthRatio = 1.43;
        this.shortHeightPercentage = 0.25;
        this.longHeightPercentage = 0.625;
    }

    /**
     * Sets the ratio of space width to bar width. The default value is {@code 1.43} (spaces are 43% wider than bars).
     *
     * @param moduleWidthRatio the ratio of space width to bar width
     */
    public void setModuleWidthRatio(double moduleWidthRatio) {
        this.moduleWidthRatio = moduleWidthRatio;
    }

    /**
     * Returns the ratio of space width to bar width.
     *
     * @return the ratio of space width to bar width
     */
    public double getModuleWidthRatio() {
        return moduleWidthRatio;
    }

    /**
     * Sets the percentage of the full symbol height used for short bars (0 - 1). The default value is {@code 0.25} (25% of full symbol height).
     *
     * @param shortHeightPercentage the percentage of the full symbol height used for short bars
     */
    public void setShortHeightPercentage(double shortHeightPercentage) {
        if (shortHeightPercentage < 0 || shortHeightPercentage > 1) {
            throw new IllegalArgumentException("Height percentage must be between 0 and 1.");
        }
        this.shortHeightPercentage = shortHeightPercentage;
    }

    /**
     * Returns the percentage of the full symbol height used for short bars.
     *
     * @return the percentage of the full symbol height used for short bars
     */
    public double getShortHeightPercentage() {
        return shortHeightPercentage;
    }

    /**
     * Sets the percentage of the full symbol height used for long bars (0 - 1). The default value is {@code 0.625} (62.5% of full symbol height).
     *
     * @param longHeightPercentage the percentage of the full symbol height used for long bars
     */
    public void setLongHeightPercentage(double longHeightPercentage) {
        if (longHeightPercentage < 0 || longHeightPercentage > 1) {
            throw new IllegalArgumentException("Height percentage must be between 0 and 1.");
        }
        this.longHeightPercentage = longHeightPercentage;
    }

    /**
     * Returns the percentage of the full symbol height used for long bars.
     *
     * @return the percentage of the full symbol height used for long bars
     */
    public double getLongHeightPercentage() {
        return longHeightPercentage;
    }

    @Override
    protected void encode() {
        String zip = "";
        String zip_adder;
        String tracker = "";
        int i, j;
        int length = content.length();
        BigInteger accum;
        BigInteger x_reg;
        BigInteger mask;
        int usps_crc;
        int[] codeword = new int[10];
        int[] characters = new int[10];
        boolean[] bar_map = new boolean[130];
        char c;

        if (!content.matches("[0-9\u002D]+")) {
            throw new OkapiException("Invalid characters in input");
        }

        if (length > 32) {
            throw new OkapiException("Input too long");
        }

        /* separate the tracking code from the routing code */
        j = 0;
        for (i = 0; i < length; i++) {
            if (content.charAt(i) == '-') {
                j = 1;
            } else {
                if (j == 0) {
                    /* reading tracker */
                    tracker += content.charAt(i);
                } else {
                    /* reading zip code */
                    zip += content.charAt(i);
                }
            }
        }

        if (tracker.length() != 20) {
            throw new OkapiException("Invalid length tracking code");
        }

        if (zip.length() > 11) {
            throw new OkapiException("Invalid ZIP code");
        }

        /* *** Step 1 - Conversion of Data Fields into Binary Data *** */

        /* Routing code first */
        if (zip.length() > 0) {
            x_reg = new BigInteger(zip);
        } else {
            x_reg = new BigInteger("0");
        }

        /* add weight to routing code */
        if (zip.length() > 9) {
            zip_adder = "1000100001";
        } else {
            if (zip.length() > 5) {
                zip_adder = "100001";
            } else {
                if (zip.length() > 0) {
                    zip_adder = "1";
                } else {
                    zip_adder = "0";
                }
            }
        }

        accum = new BigInteger(zip_adder);
        accum = accum.add(x_reg);
        accum = accum.multiply(BigInteger.valueOf(10));
        accum = accum.add(BigInteger.valueOf(Character.getNumericValue(tracker.charAt(0))));
        accum = accum.multiply(BigInteger.valueOf(5));
        accum = accum.add(BigInteger.valueOf(Character.getNumericValue(tracker.charAt(1))));
        for (i = 2; i < tracker.length(); i++) {
            accum = accum.multiply(BigInteger.valueOf(10));
            accum = accum.add(BigInteger.valueOf(Character.getNumericValue(tracker.charAt(i))));
        }

        /* *** Step 2 - Generation of 11-bit CRC on Binary Data *** */

        int[] byte_array = new int[13];
        for (i = 0; i < byte_array.length; i++) {
            mask = accum.shiftRight(96 - (8 * i));
            mask = mask.and(new BigInteger("255"));
            byte_array[i] = mask.intValue();
        }

        usps_crc = USPS_MSB_Math_CRC11GenerateFrameCheckSequence(byte_array);

        /* *** Step 3 - Conversion from Binary Data to Codewords *** */

        /* start with codeword J which is base 636 */
        x_reg = accum.mod(BigInteger.valueOf(636));
        codeword[9] = x_reg.intValue();
        accum = accum.subtract(x_reg);
        accum = accum.divide(BigInteger.valueOf(636));

        for (i = 8; i >= 0; i--) {
            x_reg = accum.mod(BigInteger.valueOf(1365));
            codeword[i] = x_reg.intValue();
            accum = accum.subtract(x_reg);
            accum = accum.divide(BigInteger.valueOf(1365));
        }

        for (i = 0; i < 9; i++) {
            if (codeword[i] == 1365) {
                codeword[i] = 0;
            }
        }

        /* *** Step 4 - Inserting Additional Information into Codewords *** */

        codeword[9] = codeword[9] * 2;

        if (usps_crc >= 1024) {
            codeword[0] += 659;
        }

        info("Codewords: ");
        for (i = 0; i < 10; i++) {
            infoSpace(codeword[i]);
        }
        infoLine();

        /* *** Step 5 - Conversion from Codewords to Characters *** */

        for (i = 0; i < 10; i++) {
            if (codeword[i] < 1287) {
                characters[i] = APPX_D_I[codeword[i]];
            } else {
                characters[i] = APPX_D_II[codeword[i] - 1287];
            }
        }

        for (i = 0; i < 10; i++) {
            if ((usps_crc & (1 << i)) != 0) {
                characters[i] = 0x1FFF - characters[i];
            }
        }

        /* *** Step 6 - Conversion from Characters to the Intelligent Mail Barcode *** */

        for (i = 0; i < 10; i++) {
            for (j = 0; j < 13; j++) {
                if ((characters[i] & (1 << j)) == 0) {
                    bar_map[APPX_D_IV[(13 * i) + j] - 1] = false;
                } else {
                    bar_map[APPX_D_IV[(13 * i) + j] - 1] = true;
                }
            }
        }

        readable = formatHumanReadableText(content);
        pattern = new String[1];
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;

        pattern[0] = "";
        for (i = 0; i < 65; i++) {
            c = 'T';
            if (bar_map[i]) {
                c = 'D';
            }
            if (bar_map[i + 65]) {
                c = 'A';
            }
            if (bar_map[i] && bar_map[i + 65]) {
                c = 'F';
            }
            pattern[0] += c;
        }

        infoLine("Encoding: " + pattern[0]);
    }

    private static int USPS_MSB_Math_CRC11GenerateFrameCheckSequence(int[] bytes) {

        int generatorPolynomial = 0x0F35;
        int frameCheckSequence = 0x07FF;
        int data;
        int byteIndex, bit;
        int byteArrayPtr = 0;

        /* Do most significant byte skipping the 2 most significant bits */
        data = bytes[byteArrayPtr] << 5;
        byteArrayPtr++;
        for (bit = 2; bit < 8; bit++) {
            if (((frameCheckSequence ^ data) & 0x400) != 0)
                frameCheckSequence = (frameCheckSequence << 1) ^ generatorPolynomial;
            else
                frameCheckSequence = (frameCheckSequence << 1);
            frameCheckSequence &= 0x7FF;
            data <<= 1;
        }

        /* Do rest of the bytes */
        for (byteIndex = 1; byteIndex < 13; byteIndex++) {
            data = bytes[byteArrayPtr] << 3;
            byteArrayPtr++;
            for (bit = 0; bit < 8; bit++) {
                if (((frameCheckSequence ^ data) & 0x0400) != 0) {
                    frameCheckSequence = (frameCheckSequence << 1) ^ generatorPolynomial;
                } else {
                    frameCheckSequence = (frameCheckSequence << 1);
                }
                frameCheckSequence &= 0x7FF;
                data <<= 1;
            }
        }

        return frameCheckSequence;
    }

    /**
     * <p>Formats the barcode content into the correct human-readable format, per section 2.4.3 of the spec:
     *
     * <p>The human-readable information, when required, shall consist of the 20-digit tracking code and the 5-, 9-, or 11-digit
     * routing code, if present. The fields of the tracking code, as defined in 2.1.3, shall be separated with a space added
     * between data fields. When the barcode contains a routing code, the 5-digit ZIP Code, the 4-digit add-on, and the
     * remaining 2 digits shall be separated with a space added between data fields.
     *
     * <p>Appendix F contains a good overview of the different IMb constructs / formats.
     *
     * @param content the content to be formatted
     * @return the formatted content
     */
    protected static String formatHumanReadableText(String content) {
        StringBuilder hrt = new StringBuilder(50);
        boolean mid9 = false; // 9-digit mailer ID instead of 6-digit mailer ID
        boolean tracing = true; // STID indicates Origin IMb Tracing Services (050, 052)
        boolean pimb = true; // barcode identifier (BI) is 94, indicating pIMb
        boolean mpe5 = false; // if MPE = 5, it's a CFS/RFS variant of pIMb
        int i = 0;
        for (char c : content.toCharArray()) {
            if (c < '0' || c > '9') {
                continue;
            }
            if (i == 5 && c == '9') {
                mid9 = true;
            }
            if ((i == 2 && c != '0') || (i == 3 && c != '5') || (i == 4 && c != '0' && c != '2')) {
                tracing = false;
            }
            if ((i == 0 && c != '9') || (i == 1 && c != '4')) {
                pimb = false;
            }
            if (i == 5 && c == '5') {
                mpe5 = true;
            }
            if ((i == 2) // BI -> STID
             || (i == 5) // STID -> ...
             || (i == 6 && pimb)
             || (i == 10 && pimb)
             || (i == 13 && pimb && !mpe5)
             || (i == 15 && pimb && !mpe5)
             || (i == 11 && !mid9 && !tracing && !pimb)
             || (i == 14 && mid9 && !tracing && !pimb)
             || (i == 20) // ... -> zip-5
             || (i == 25) // zip-5 -> zip-4
             || (i == 29)) { // zip-4 -> zip-2
                hrt.append(' ');
            }
            hrt.append(c);
            i++;
        }
        return hrt.toString().trim();
    }

    @Override
    protected void plotSymbol() {
        int xBlock, shortHeight, longHeight;
        double x, y, w, h, dx;

        rectangles.clear();
        texts.clear();

        int baseY;
        if (humanReadableLocation == TOP) {
            baseY = getTheoreticalHumanReadableHeight();
        } else {
            baseY = 0;
        }

        x = 0;
        w = moduleWidth;
        y = 0;
        h = 0;
        dx = (1 + moduleWidthRatio) * w;
        shortHeight = (int) (shortHeightPercentage * default_height);
        longHeight = (int) (longHeightPercentage * default_height);
        for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {

            switch (pattern[0].charAt(xBlock)) {
            case 'A':
                y = baseY;
                h = longHeight;
                break;
            case 'D':
                y = baseY + default_height - longHeight;
                h = longHeight;
                break;
            case 'F':
                y = baseY;
                h = default_height;
                break;
            case 'T':
                y = baseY + default_height - longHeight;
                h = shortHeight;
                break;
            }

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

            x += dx;
        }

        symbol_width = (int) Math.ceil(((pattern[0].length() - 1) * dx) + w); // final bar doesn't need extra whitespace
        symbol_height = default_height;

        if (humanReadableLocation != NONE && !readable.isEmpty()) {
            double baseline;
            if (humanReadableLocation == TOP) {
                baseline = fontSize;
            } else {
                baseline = symbol_height + fontSize;
            }
            texts.add(new TextBox(0, baseline, symbol_width, readable, humanReadableAlignment));
        }
    }
}
 public class UspsPackage extends Symbol {

    @Override
    protected void encode() {

        if (!content.matches("[0-9\\[\\]]+")) {
            /* Input must be numeric only */
            throw new OkapiException("Invalid IMpb data");
        }

        if (content.length() % 2 != 0) {
            /* Input must be even length */
            throw new OkapiException("Invalid IMpb data");
        }

        Code128 code128 = new Code128();
        code128.unsetCc();
        code128.setDataType(DataType.GS1);
        code128.setContent(content);

        boolean fourTwenty = content.length() > 4 &&
                             content.charAt(1) == '4' &&
                             content.charAt(2) == '2' &&
                             content.charAt(3) == '0';

        String hrt = "";
        int bracketCount = 0;
        for (int i = 0; i < content.length(); i++) {
            if (content.charAt(i) == '[') {
                bracketCount++;
            }
            if (!(fourTwenty && bracketCount < 2)) {
                if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) {
                    hrt += content.charAt(i);
                }
            }
        }

        String spacedHrt = "";
        for(int i = 0; i < hrt.length(); i++) {
            spacedHrt += hrt.charAt(i);
            if (i % 4 == 3) {
                spacedHrt += " ";
            }
        }

        encodeInfo = code128.encodeInfo;
        readable = spacedHrt;
        pattern = new String[1];
        pattern[0] = code128.pattern[0];
        row_count = 1;
        row_height = new int[1];
        row_height[0] = -1;
    }

    @Override
    protected void plotSymbol() {
        int xBlock;
        int x, y, w, h;
        boolean black;
        int offset = 20;
        int yoffset = 15;
        String banner = "USPS TRACKING #";

        rectangles.clear();
        texts.clear();
        y = yoffset;
        h = 0;
        black = true;
        x = 0;
        for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {
            w = pattern[0].charAt(xBlock) - '0';
            if (black) {
                if (row_height[0] == -1) {
                    h = default_height;
                } else {
                    h = row_height[0];
                }
                if (w != 0 && h != 0) {
                    Rectangle2D.Double rect = new Rectangle2D.Double(x + offset, y, w, h);
                    rectangles.add(rect);
                }
                symbol_width = x + w + (2 * offset);
            }
            black = !black;
            x += w;
        }
        symbol_height = h + (2 * yoffset);

        // Add boundary bars
        Rectangle2D.Double topBar = new Rectangle2D.Double(0, 0, symbol_width, 2);
        Rectangle2D.Double bottomBar = new Rectangle2D.Double(0, symbol_height - 2, symbol_width, 2);
        rectangles.add(topBar);
        rectangles.add(bottomBar);

        texts.add(new TextBox(0, symbol_height - 6.0, symbol_width, readable, humanReadableAlignment));
        texts.add(new TextBox(0, 12.0, symbol_width, banner, humanReadableAlignment));
    }

    /** {@inheritDoc} */
    @Override
    protected int[] getCodewords() {
        return getPatternAsCodewords(6);
    }
}
 public class Java2DRenderer implements SymbolRenderer {

    /** The graphics to render to. */
    private final Graphics2D g2d;

    /** The magnification factor to apply. */
    private final double magnification;

    /** The paper (background) color. */
    private final Color paper;

    /** The ink (foreground) color. */
    private final Color ink;

    /**
     * Creates a new Java 2D renderer. If the specified paper color is {@code null}, the symbol is drawn without clearing the
     * existing {@code g2d} background.
     *
     * @param g2d the graphics to render to
     * @param magnification the magnification factor to apply
     * @param paper the paper (background) color (may be {@code null})
     * @param ink the ink (foreground) color
     */
    public Java2DRenderer(Graphics2D g2d, double magnification, Color paper, Color ink) {
        this.g2d = g2d;
        this.magnification = magnification;
        this.paper = paper;
        this.ink = ink;
    }

    /** {@inheritDoc} */
    @Override
    public void render(Symbol symbol) {

        int marginX = (int) (symbol.getQuietZoneHorizontal() * magnification);
        int marginY = (int) (symbol.getQuietZoneVertical() * magnification);

        Font f = symbol.getFont();
        if (f != null) {
            f = f.deriveFont((float) (f.getSize2D() * magnification));
        } else {
            f = new Font(symbol.getFontName(), Font.PLAIN, (int) (symbol.getFontSize() * magnification));
            f = f.deriveFont(Collections.singletonMap(TextAttribute.TRACKING, 0));
        }

        Font oldFont = g2d.getFont();
        Color oldColor = g2d.getColor();

        if (paper != null) {
            int w = (int) (symbol.getWidth() * magnification);
            int h = (int) (symbol.getHeight() * magnification);
            g2d.setColor(paper);
            g2d.fillRect(0, 0, w, h);
        }

        g2d.setColor(ink);

        for (Rectangle2D.Double rect : symbol.getRectangles()) {
            double x = (rect.x * magnification) + marginX;
            double y = (rect.y * magnification) + marginY;
            double w = rect.width * magnification;
            double h = rect.height * magnification;
            g2d.fillRect((int) x, (int) y, (int) w, (int) h);
        }

        for (TextBox text : symbol.getTexts()) {
            HumanReadableAlignment alignment = (text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment);
            Font font = (alignment != JUSTIFY ? f : addTracking(f, text.width * magnification, text.text, g2d));
            g2d.setFont(font);
            FontMetrics fm = g2d.getFontMetrics();
            Rectangle2D bounds = fm.getStringBounds(text.text, g2d);
            float y = (float) (text.y * magnification) + marginY;
            float x;
            switch (alignment) {
                case LEFT:
                case JUSTIFY:
                    x = (float) ((magnification * text.x) + marginX);
                    break;
                case RIGHT:
                    x = (float) ((magnification * text.x) + (magnification * text.width) - bounds.getWidth() + marginX);
                    break;
                case CENTER:
                    x = (float) ((magnification * text.x) + (magnification * text.width / 2) - (bounds.getWidth() / 2) + marginX);
                    break;
                default:
                    throw new IllegalStateException("Unknown alignment: " + alignment);
            }
            g2d.drawString(text.text, x, y);
        }

        for (Hexagon hexagon : symbol.getHexagons()) {
            Polygon polygon = new Polygon();
            for (int j = 0; j < 6; j++) {
                polygon.addPoint((int) ((hexagon.pointX[j] * magnification) + marginX),
                        (int) ((hexagon.pointY[j] * magnification) + marginY));
            }
            g2d.fill(polygon);
        }

        List< Ellipse2D.Double > target = symbol.getTarget();
        for (int i = 0; i + 1 < target.size(); i += 2) {
            Ellipse2D.Double outer = adjust(target.get(i), magnification, marginX, marginY);
            Ellipse2D.Double inner = adjust(target.get(i + 1), magnification, marginX, marginY);
            Area area = new Area(outer);
            area.subtract(new Area(inner));
            g2d.fill(area);
        }

        g2d.setFont(oldFont);
        g2d.setColor(oldColor);
    }

    private static Ellipse2D.Double adjust(Ellipse2D.Double ellipse, double magnification, int marginX, int marginY) {
        double x = (ellipse.x * magnification) + marginX;
        double y = (ellipse.y * magnification) + marginY;
        double w = (ellipse.width * magnification) + marginX;
        double h = (ellipse.height * magnification) + marginY;
        return new Ellipse2D.Double(x, y, w, h);
    }

    private static Font addTracking(Font baseFont, double maxTextWidth, String text, Graphics2D g2d) {
        FontRenderContext frc = g2d.getFontRenderContext();
        double originalWidth = baseFont.getStringBounds(text, frc).getWidth();
        double extraSpace = maxTextWidth - originalWidth;
        double extraSpacePerGap = extraSpace / (text.length() - 1);
        double scaleX = (baseFont.isTransformed() ? baseFont.getTransform().getScaleX() : 1);
        double tracking = extraSpacePerGap / (baseFont.getSize2D() * scaleX);
        return baseFont.deriveFont(Collections.singletonMap(TextAttribute.TRACKING, tracking));
    }
} public class PostScriptRenderer implements SymbolRenderer {

    /** The output stream to render to. */
    private final OutputStream out;

    /** The magnification factor to apply. */
    private final double magnification;

    /** The paper (background) color. */
    private final Color paper;

    /** The ink (foreground) color. */
    private final Color ink;

    /**
     * Creates a new PostScript renderer.
     *
     * @param out the output stream to render to
     * @param magnification the magnification factor to apply
     * @param paper the paper (background) color
     * @param ink the ink (foreground) color
     */
    public PostScriptRenderer(OutputStream out, double magnification, Color paper, Color ink) {
        this.out = out;
        this.magnification = magnification;
        this.paper = paper;
        this.ink = ink;
    }

    /** {@inheritDoc} */
    @Override
    public void render(Symbol symbol) throws IOException {

        // All y dimensions are reversed because EPS origin (0,0) is at the bottom left, not top left

        String content = symbol.getContent();
        int width = (int) (symbol.getWidth() * magnification);
        int height = (int) (symbol.getHeight() * magnification);
        int marginX = (int) (symbol.getQuietZoneHorizontal() * magnification);
        int marginY = (int) (symbol.getQuietZoneVertical() * magnification);

        String title;
        if (content == null || content.isEmpty()) {
            title = "OkapiBarcode Generated Symbol";
        } else {
            title = content;
        }

        try (ExtendedOutputStreamWriter writer = new ExtendedOutputStreamWriter(out, "%.2f")) {

            // Header
            writer.append("%!PS-Adobe-3.0 EPSF-3.0\n");
            writer.append("%%Creator: OkapiBarcode\n");
            writer.append("%%Title: ").append(title).append('\n');
            writer.append("%%Pages: 0\n");
            writer.append("%%BoundingBox: 0 0 ").appendInt(width).append(" ").appendInt(height).append("\n");
            writer.append("%%EndComments\n");

            // Definitions
            writer.append("/TL { setlinewidth moveto lineto stroke } bind def\n");
            writer.append("/TC { moveto 0 360 arc 360 0 arcn fill } bind def\n");
            writer.append("/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def\n");
            writer.append("/TB { 2 copy } bind def\n");
            writer.append("/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def\n");
            writer.append("/TE { pop pop } bind def\n");

            // Background
            writer.append("newpath\n");
            writer.append(ink.getRed() / 255.0).append(" ")
                  .append(ink.getGreen() / 255.0).append(" ")
                  .append(ink.getBlue() / 255.0).append(" setrgbcolor\n");
            writer.append(paper.getRed() / 255.0).append(" ")
                  .append(paper.getGreen() / 255.0).append(" ")
                  .append(paper.getBlue() / 255.0).append(" setrgbcolor\n");
            writer.append(height).append(" 0.00 TB 0.00 ").append(width).append(" TR\n");

            // Rectangles
            for (int i = 0; i < symbol.getRectangles().size(); i++) {
                Rectangle2D.Double rect = symbol.getRectangles().get(i);
                if (i == 0) {
                    writer.append("TE\n");
                    writer.append(ink.getRed() / 255.0).append(" ")
                          .append(ink.getGreen() / 255.0).append(" ")
                          .append(ink.getBlue() / 255.0).append(" setrgbcolor\n");
                    writer.append(rect.height * magnification).append(" ")
                          .append(height - ((rect.y + rect.height) * magnification) - marginY).append(" TB ")
                          .append((rect.x * magnification) + marginX).append(" ")
                          .append(rect.width * magnification).append(" TR\n");
                } else {
                    Rectangle2D.Double prev = symbol.getRectangles().get(i - 1);
                    if (!roughlyEqual(rect.height, prev.height) || !roughlyEqual(rect.y, prev.y)) {
                        writer.append("TE\n");
                        writer.append(ink.getRed() / 255.0).append(" ")
                              .append(ink.getGreen() / 255.0).append(" ")
                              .append(ink.getBlue() / 255.0).append(" setrgbcolor\n");
                        writer.append(rect.height * magnification).append(" ")
                              .append(height - ((rect.y + rect.height) * magnification) - marginY).append(" ");
                    }
                    writer.append("TB ").append((rect.x * magnification) + marginX).append(" ").append(rect.width * magnification).append(" TR\n");
                }
            }

            // Text
            for (int i = 0; i < symbol.getTexts().size(); i++) {
                TextBox text = symbol.getTexts().get(i);
                HumanReadableAlignment alignment = (text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment);
                if (i == 0) {
                    writer.append("TE\n");;
                    writer.append(ink.getRed() / 255.0).append(" ")
                          .append(ink.getGreen() / 255.0).append(" ")
                          .append(ink.getBlue() / 255.0).append(" setrgbcolor\n");
                }
                writer.append("matrix currentmatrix\n");
                writer.append("/").append(symbol.getFontName()).append(" findfont\n");
                writer.append(symbol.getFontSize() * magnification).append(" scalefont setfont\n");
                double y = height - (text.y * magnification) - marginY;
                switch (alignment) {
                    case LEFT:
                        double leftX = (magnification * text.x) + marginX;
                        writer.append(" 0 0 moveto ").append(leftX).append(" ").append(y)
                              .append(" translate 0.00 rotate 0 0 moveto\n");
                        writer.append(" (").append(text.text).append(") show\n");
                        break;
                    case JUSTIFY:
                        double textX = (magnification * text.x) + marginX;
                        double textW = (magnification * text.width);
                        writer.append(" 0 0 moveto ").append(textX).append(" ").append(y)
                              .append(" translate 0.00 rotate 0 0 moveto\n");
                        writer.append(" (").append(text.text).append(") dup stringwidth pop ")
                              .append(textW).append(" sub neg 1 index length 1 sub div 0")
                              .append(" 3 -1 roll ashow\n");
                        break;
                    case RIGHT:
                        double rightX = (magnification * text.x) + (magnification * text.width) + marginX;
                        writer.append(" 0 0 moveto ").append(rightX).append(" ").append(y)
                              .append(" translate 0.00 rotate 0 0 moveto\n");
                        writer.append(" (").append(text.text).append(") stringwidth\n");
                        writer.append("pop\n");
                        writer.append("-1 mul 0 rmoveto\n");
                        writer.append(" (").append(text.text).append(") show\n");
                        break;
                    case CENTER:
                        double centerX = (magnification * text.x) + (magnification * text.width / 2) + marginX;
                        writer.append(" 0 0 moveto ").append(centerX).append(" ").append(y)
                              .append(" translate 0.00 rotate 0 0 moveto\n");
                        writer.append(" (").append(text.text).append(") stringwidth\n");
                        writer.append("pop\n");
                        writer.append("-2 div 0 rmoveto\n");
                        writer.append(" (").append(text.text).append(") show\n");
                        break;
                    default:
                        throw new IllegalStateException("Unknown alignment: " + alignment);
                }
                writer.append("setmatrix\n");
            }

            // Circles
            // Because MaxiCode size is fixed, this ignores magnification
            for (int i = 0; i < symbol.getTarget().size(); i += 2) {
                Ellipse2D.Double ellipse1 = symbol.getTarget().get(i);
                Ellipse2D.Double ellipse2 = symbol.getTarget().get(i + 1);
                if (i == 0) {
                    writer.append("TE\n");
                    writer.append(ink.getRed() / 255.0).append(" ")
                          .append(ink.getGreen() / 255.0).append(" ")
                          .append(ink.getBlue() / 255.0).append(" setrgbcolor\n");
                    writer.append(ink.getRed() / 255.0).append(" ")
                          .append(ink.getGreen() / 255.0).append(" ")
                          .append(ink.getBlue() / 255.0).append(" setrgbcolor\n");
                }
                double x1 = ellipse1.x + (ellipse1.width / 2);
                double x2 = ellipse2.x + (ellipse2.width / 2);
                double y1 = height - ellipse1.y - (ellipse1.width / 2);
                double y2 = height - ellipse2.y - (ellipse2.width / 2);
                double r1 = ellipse1.width / 2;
                double r2 = ellipse2.width / 2;
                writer.append(x1 + marginX)
                      .append(" ").append(y1 - marginY)
                      .append(" ").append(r1)
                      .append(" ").append(x2 + marginX)
                      .append(" ").append(y2 - marginY)
                      .append(" ").append(r2)
                      .append(" ").append(x2 + r2 + marginX)
                      .append(" ").append(y2 - marginY)
                      .append(" TC\n");
            }

            // Hexagons
            // Because MaxiCode size is fixed, this ignores magnification
            for (int i = 0; i < symbol.getHexagons().size(); i++) {
                Hexagon hexagon = symbol.getHexagons().get(i);
                for (int j = 0; j < 6; j++) {
                    writer.append(hexagon.pointX[j] + marginX).append(" ").append((height - hexagon.pointY[j]) - marginY).append(" ");
                }
                writer.append(" TH\n");
            }

            // Footer
            writer.append("\nshowpage\n");
        }
    }
} public class SvgRenderer implements SymbolRenderer {

    /** The output stream to render to. */
    private final OutputStream out;

    /** The magnification factor to apply. */
    private final double magnification;

    /** The paper (background) color. */
    private final Color paper;

    /** The ink (foreground) color. */
    private final Color ink;

    /** Whether or not to include the XML prolog in the output. */
    private final boolean xmlProlog;

    /**
     * Creates a new SVG renderer.
     *
     * @param out the output stream to render to
     * @param magnification the magnification factor to apply
     * @param paper the paper (background) color
     * @param ink the ink (foreground) color
     * @param xmlProlog whether or not to include the XML prolog in the output (usually {@code true} for
     *        standalone SVG documents, {@code false} for SVG content embedded directly in HTML documents)
     */
    public SvgRenderer(OutputStream out, double magnification, Color paper, Color ink, boolean xmlProlog) {
        this.out = out;
        this.magnification = magnification;
        this.paper = paper;
        this.ink = ink;
        this.xmlProlog = xmlProlog;
    }

    /** {@inheritDoc} */
    @Override
    public void render(Symbol symbol) throws IOException {

        String content = symbol.getContent();
        int width = (int) (symbol.getWidth() * magnification);
        int height = (int) (symbol.getHeight() * magnification);
        int marginX = (int) (symbol.getQuietZoneHorizontal() * magnification);
        int marginY = (int) (symbol.getQuietZoneVertical() * magnification);

        String title;
        if (content == null || content.isEmpty()) {
            title = "OkapiBarcode Generated Symbol";
        } else {
            title = content;
        }

        String fgColour = String.format("%02X", ink.getRed())
                        + String.format("%02X", ink.getGreen())
                        + String.format("%02X", ink.getBlue());

        String bgColour = String.format("%02X", paper.getRed())
                        + String.format("%02X", paper.getGreen())
                        + String.format("%02X", paper.getBlue());

        try (ExtendedOutputStreamWriter writer = new ExtendedOutputStreamWriter(out, "%.2f")) {

            // XML Prolog
            if(xmlProlog) {
                writer.append("<?xml version=\"1.0\" standalone=\"no\"?>\n");
                writer.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n");
                writer.append("   \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
            }

            // Header
            writer.append("<svg width=\"").appendInt(width)
                  .append("\" height=\"").appendInt(height)
                  .append("\" version=\"1.1")
                  .append("\" xmlns=\"http://www.w3.org/2000/svg\">\n");
            writer.append("   <desc>").append(clean(title)).append("</desc>\n");
            writer.append("   <g id=\"barcode\" fill=\"#").append(fgColour).append("\">\n");
            writer.append("      <rect x=\"0\" y=\"0\" width=\"").appendInt(width)
                  .append("\" height=\"").appendInt(height)
                  .append("\" fill=\"#").append(bgColour).append("\" />\n");

            // Rectangles
            for (int i = 0; i < symbol.getRectangles().size(); i++) {
                Rectangle2D.Double rect = symbol.getRectangles().get(i);
                writer.append("      <rect x=\"").append((rect.x * magnification) + marginX)
                      .append("\" y=\"").append((rect.y * magnification) + marginY)
                      .append("\" width=\"").append(rect.width * magnification)
                      .append("\" height=\"").append(rect.height * magnification)
                      .append("\" />\n");
            }

            // Text
            for (int i = 0; i < symbol.getTexts().size(); i++) {
                TextBox text = symbol.getTexts().get(i);
                HumanReadableAlignment alignment = (text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment);
                double x;
                String anchor;
                switch (alignment) {
                    case LEFT:
                    case JUSTIFY:
                        x = (magnification * text.x) + marginX;
                        anchor = "start";
                        break;
                    case RIGHT:
                        x = (magnification * text.x) + (magnification * text.width) + marginX;
                        anchor = "end";
                        break;
                    case CENTER:
                        x = (magnification * text.x) + (magnification * text.width / 2) + marginX;
                        anchor = "middle";
                        break;
                    default:
                        throw new IllegalStateException("Unknown alignment: " + alignment);
                }
                writer.append("      <text x=\"").append(x)
                      .append("\" y=\"").append((text.y * magnification) + marginY)
                      .append("\" text-anchor=\"").append(anchor).append("\"\n");
                if (alignment == JUSTIFY) {
                    writer.append("         textLength=\"")
                          .append(text.width * magnification)
                          .append("\" lengthAdjust=\"spacing\"\n");
                }
                writer.append("         font-family=\"").append(clean(symbol.getFontName()))
                      .append("\" font-size=\"").append(symbol.getFontSize() * magnification)
                      .append("\" fill=\"#").append(fgColour).append("\">\n");
                writer.append("         ").append(clean(text.text)).append("\n");
                writer.append("      </text>\n");
            }

            // Circles
            for (int i = 0; i < symbol.getTarget().size(); i++) {
                Ellipse2D.Double ellipse = symbol.getTarget().get(i);
                String color;
                if ((i & 1) == 0) {
                    color = fgColour;
                } else {
                    color = bgColour;
                }
                writer.append("      <circle cx=\"").append(((ellipse.x + (ellipse.width / 2)) * magnification) + marginX)
                      .append("\" cy=\"").append(((ellipse.y + (ellipse.width / 2)) * magnification) + marginY)
                      .append("\" r=\"").append((ellipse.width / 2) * magnification)
                      .append("\" fill=\"#").append(color).append("\" />\n");
            }

            // Hexagons
            for (int i = 0; i < symbol.getHexagons().size(); i++) {
                Hexagon hexagon = symbol.getHexagons().get(i);
                writer.append("      <path d=\"");
                for (int j = 0; j < 6; j++) {
                    if (j == 0) {
                        writer.append("M ");
                    } else {
                        writer.append("L ");
                    }
                    writer.append((hexagon.pointX[j] * magnification) + marginX).append(" ")
                          .append((hexagon.pointY[j] * magnification) + marginY).append(" ");
                }
                writer.append("Z\" />\n");
            }

            // Footer
            writer.append("   </g>\n");
            writer.append("</svg>\n");
        }
    }

    /**
     * Cleans / sanitizes the specified string for inclusion in XML. A bit convoluted, but we're
     * trying to do it without adding an external dependency just for this...
     *
     * @param s the string to be cleaned / sanitized
     * @return the cleaned / sanitized string
     */
    protected String clean(String s) {

        // remove control characters
        s = s.replaceAll("[\u0000-\u001f]", "");

        // escape XML characters
        try {
            Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
            Text text = document.createTextNode(s);
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            DOMSource source = new DOMSource(text);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(source, result);
            return writer.toString();
        } catch (ParserConfigurationException | TransformerException | TransformerFactoryConfigurationError e) {
            return s;
        }
    }
}
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import uk.org.okapibarcode.backend.AustraliaPost;
import uk.org.okapibarcode.backend.AztecCode;
import uk.org.okapibarcode.backend.AztecRune;
import uk.org.okapibarcode.backend.ChannelCode;
import uk.org.okapibarcode.backend.Codabar;
import uk.org.okapibarcode.backend.CodablockF;
import uk.org.okapibarcode.backend.Code11;
import uk.org.okapibarcode.backend.Code128;
import uk.org.okapibarcode.backend.Code16k;
import uk.org.okapibarcode.backend.Code2Of5;
import uk.org.okapibarcode.backend.Code2Of5.ToFMode;
import uk.org.okapibarcode.backend.Code32;
import uk.org.okapibarcode.backend.Code3Of9;
import uk.org.okapibarcode.backend.Code3Of9Extended;
import uk.org.okapibarcode.backend.Code49;
import uk.org.okapibarcode.backend.Code93;
import uk.org.okapibarcode.backend.CodeOne;
import uk.org.okapibarcode.backend.Composite;
import uk.org.okapibarcode.backend.DataBar14;
import uk.org.okapibarcode.backend.DataBar14.Mode;
import uk.org.okapibarcode.backend.DataBarExpanded;
import uk.org.okapibarcode.backend.DataBarLimited;
import uk.org.okapibarcode.backend.DataMatrix;
import uk.org.okapibarcode.backend.DataMatrix.ForceMode;
import uk.org.okapibarcode.backend.Ean;
import uk.org.okapibarcode.backend.GridMatrix;
import uk.org.okapibarcode.backend.HumanReadableLocation;
import uk.org.okapibarcode.backend.JapanPost;
import uk.org.okapibarcode.backend.KixCode;
import uk.org.okapibarcode.backend.KoreaPost;
import uk.org.okapibarcode.backend.Logmars;
import uk.org.okapibarcode.backend.MaxiCode;
import uk.org.okapibarcode.backend.MicroQrCode;
import uk.org.okapibarcode.backend.MsiPlessey;
import uk.org.okapibarcode.backend.Nve18;
import uk.org.okapibarcode.backend.OkapiException;
import uk.org.okapibarcode.backend.Pdf417;
import uk.org.okapibarcode.backend.Pharmacode;
import uk.org.okapibarcode.backend.Pharmacode2Track;
import uk.org.okapibarcode.backend.Pharmazentralnummer;
import uk.org.okapibarcode.backend.Postnet;
import uk.org.okapibarcode.backend.QrCode;
import uk.org.okapibarcode.backend.RoyalMail4State;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.backend.Telepen;
import uk.org.okapibarcode.backend.Upc;
import uk.org.okapibarcode.backend.UspsOneCode;
import uk.org.okapibarcode.backend.UspsPackage;
import uk.org.okapibarcode.output.Java2DRenderer;
import uk.org.okapibarcode.output.PostScriptRenderer;
import uk.org.okapibarcode.output.SvgRenderer;
/*
 * Copyright 2015 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;



/**
 *
 * @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
 */
public class MakeBarcode {

    public void process(Settings settings, String dataInput, String outputFileName) {
        int type = settings.getSymbolType();
        Symbol symbol;
        String extension = "";
        HumanReadableLocation hrtLocation = settings.getHrtPosition();

        Color ink = settings.getForegroundColour();
        Color paper = settings.getBackgroundColour();

        if (settings.isReverseColour()) {
            ink = Color.WHITE;
            paper = Color.BLACK;
        }

        try {
            /* values marked "Legacy" are for compatability purposes
               and should not be documented.
            */
            switch(type) {
                case 1:
                    // Code 11
                    Code11 code11 = new Code11();
                    code11.setHumanReadableLocation(hrtLocation);
                    code11.setContent(dataInput);
                    symbol = code11;
                    break;
                case 2:
                    // Code 2 of 5
                    Code2Of5 c25matrix = new Code2Of5();
                    c25matrix.setMode(ToFMode.MATRIX);
                    c25matrix.setHumanReadableLocation(hrtLocation);
                    c25matrix.setContent(dataInput);
                    symbol = c25matrix;
                    break;
                case 3:
                    //Interleaved 2 of 5
                    Code2Of5 c25inter = new Code2Of5();
                    c25inter.setMode(ToFMode.INTERLEAVED);
                    c25inter.setHumanReadableLocation(hrtLocation);
                    c25inter.setContent(dataInput);
                    symbol = c25inter;
                    break;
                case 4:
                    // IATA 2 of 5
                    Code2Of5 c25iata = new Code2Of5();
                    c25iata.setMode(ToFMode.IATA);
                    c25iata.setHumanReadableLocation(hrtLocation);
                    c25iata.setContent(dataInput);
                    symbol = c25iata;
                    break;
                case 6:
                    // Data Logic
                    Code2Of5 c25logic = new Code2Of5();
                    c25logic.setMode(ToFMode.DATA_LOGIC);
                    c25logic.setHumanReadableLocation(hrtLocation);
                    c25logic.setContent(dataInput);
                    symbol = c25logic;
                    break;
                case 7:
                    // Industrial 2 of 5
                    Code2Of5 c25ind = new Code2Of5();
                    c25ind.setMode(ToFMode.INDUSTRIAL);
                    c25ind.setHumanReadableLocation(hrtLocation);
                    c25ind.setContent(dataInput);
                    symbol = c25ind;
                    break;
                case 8:
                case 99:
                case 101: // Legacy
                    // Code 39
                    Code3Of9 code3of9 = new Code3Of9();
                    if ((type == 99) || (type == 101)) {
                        code3of9.setDataType(Symbol.DataType.HIBC);
                    }
                    code3of9.setHumanReadableLocation(hrtLocation);
                    code3of9.setContent(dataInput);
                    symbol = code3of9;
                    break;
                case 9:
                    // Extended Code 39
                    Code3Of9Extended code3of9ext = new Code3Of9Extended();
                    code3of9ext.setHumanReadableLocation(hrtLocation);
                    code3of9ext.setContent(dataInput);
                    symbol = code3of9ext;
                    break;
                case 13:
                case 14: // Legacy
                case 15: // Legacy
                case 10: // Legacy
                case 11: // Legacy
                case 12: // Legacy
                    // EAN
                    Ean ean = new Ean();
                    if (eanCalculateVersion(dataInput) == 8) {
                        ean.setMode(Ean.Mode.EAN8);