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

Implements GS1 DataBar Expanded Omnidirectional and GS1 DataBar Expanded Stacked * Omnidirectional according to ISO/IEC 24724:2011. * *

DataBar expanded encodes GS1 data in either a linear or stacked format. * * @author Robin Stuart */ 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 }; protected enum EncodeMode { NUMERIC, ALPHA, ISOIEC, ANY_ENC, ALPHA_OR_ISO } private boolean linkageFlag; private int preferredColumns = 2; private boolean stacked = true; public DataBarExpanded() { inputDataType = DataType.GS1; } @Override public boolean supportsGs1() { 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 OkapiInputException.invalidCharactersInInput(); } 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 lastMode = EncodeMode.NUMERIC; int remainder, d1, d2, value; String padstring; /* 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 (int 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 (int 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 (int i = 0; i < read_posn; i++) { if (inputData[i] < '0' || inputData[i] > '9') { /* Something is wrong */ throw OkapiInputException.invalidCharactersInInput(); } } /* Now encode the compressed data field */ if (encodingMethod == 1) { /* Encoding method field "1" - general item identification data */ binaryAppend(binaryString, inputData[2] - '0', 4); for (int 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 (int 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 (int 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 (int i = 1; i < 5; i++) { int group = parseInt(inputData, i * 3, 3); binaryAppend(binaryString, group, 10); } int weight = inputData[19] - '0'; for (int 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) { EncodeMode[] generalFieldType = getInitialEncodeModes(generalField); boolean trailingDigit = applyGeneralFieldRules(generalFieldType); // modifies generalFieldType lastMode = appendToBinaryString(generalField, generalFieldType, trailingDigit, false, binaryString); // modifies binaryString remainder = calculateRemainder(binaryString.length(), stacked, blocksPerRow); if (trailingDigit) { /* There is still one more numeric digit to encode */ int i = generalField.length - 1; if (lastMode == 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 OkapiInputException.inputTooLong(); } remainder = calculateRemainder(binaryString.length(), stacked, blocksPerRow); /* Now add padding to binary string (7.2.5.5.4) */ int i = remainder; if (lastMode == 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(); } protected static EncodeMode[] getInitialEncodeModes(int[] generalField) { EncodeMode[] generalFieldType = new EncodeMode[generalField.length]; for (int 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 throw OkapiInputException.invalidCharactersInInput(); } generalFieldType[i] = mode; } for (int i = 0; i < generalField.length - 1; i++) { if (generalFieldType[i] == EncodeMode.ISOIEC && generalField[i + 1] == FNC1) { generalFieldType[i + 1] = EncodeMode.ISOIEC; } } for (int 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; } } return generalFieldType; } /** Attempts to apply encoding rules from sections 7.2.5.5.1 to 7.2.5.5.3 of ISO/IEC 24724:2006 */ protected 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; } } protected static EncodeMode appendToBinaryString(int[] generalField, EncodeMode[] generalFieldType, boolean trailingDigit, boolean treatFnc1AsNumericLatch, StringBuilder binaryString) { EncodeMode lastMode = EncodeMode.NUMERIC; int value, d1, d2; /* Set initial mode if not NUMERIC */ if (generalFieldType[0] == EncodeMode.ALPHA) { binaryString.append("0000"); /* Alphanumeric latch */ lastMode = EncodeMode.ALPHA; } if (generalFieldType[0] == EncodeMode.ISOIEC) { binaryString.append("0000"); /* Alphanumeric latch */ binaryString.append("00100"); /* ISO/IEC 646 latch */ lastMode = EncodeMode.ISOIEC; } int i = 0; int current_length = i; if (trailingDigit) { current_length++; } while (current_length < generalField.length) { switch (generalFieldType[i]) { case NUMERIC: if (lastMode != 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; lastMode = EncodeMode.NUMERIC; break; case ALPHA: if (i != 0) { if (lastMode == EncodeMode.NUMERIC) { binaryString.append("0000"); /* Alphanumeric latch */ } if (lastMode == 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); } lastMode = 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 sometimes waste 3 bits and don't perform the implicit mode change (see https://sourceforge.net/p/zint/tickets/145/) if (treatFnc1AsNumericLatch) lastMode = 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 (lastMode == EncodeMode.NUMERIC) { binaryString.append("0000"); /* Alphanumeric latch */ binaryString.append("00100"); /* ISO/IEC 646 latch */ } if (lastMode == 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); } lastMode = 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 sometimes waste 3 bits and don't perform the implicit mode change (see https://sourceforge.net/p/zint/tickets/145/) if (treatFnc1AsNumericLatch) lastMode = 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 (trailingDigit) { current_length++; } } return lastMode; } 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; } }