Dépôt officiel du code source de l'ERP OpenConcerto
Blame | Last modification | View Log | RSS feed
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
import static uk.org.okapibarcode.backend.DataBarLimited.getWidths;
import static uk.org.okapibarcode.util.Strings.binaryAppend;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* <p>
* Implements GS1 DataBar Expanded Omnidirectional and GS1 DataBar Expanded Stacked Omnidirectional
* according to ISO/IEC 24724:2011.
*
* <p>
* DataBar expanded encodes GS1 data in either a linear or stacked format.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
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() {
this.inputDataType = DataType.GS1;
}
@Override
public void setDataType(final DataType dataType) {
if (dataType != Symbol.DataType.GS1) {
throw new IllegalArgumentException("Only GS1 data type is supported for DataBar Expanded symbology.");
}
}
@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(final int columns) {
if (columns < 1 || columns > 10) {
throw new IllegalArgumentException("Invalid column count: " + columns);
}
this.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 this.preferredColumns;
}
/**
* Sets whether or not this symbology is stacked.
*
* @param stacked <tt>true</tt> for GS1 DataBar Expanded Stacked Omnidirectional, <tt>false</tt>
* for GS1 DataBar Expanded Omnidirectional
*/
public void setStacked(final boolean stacked) {
this.stacked = stacked;
}
/**
* Returns whether or not this symbology is stacked.
*
* @return <tt>true</tt> for GS1 DataBar Expanded Stacked Omnidirectional, <tt>false</tt> for
* GS1 DataBar Expanded Omnidirectional
*/
public boolean isStacked() {
return this.stacked;
}
protected void setLinkageFlag(final boolean linkageFlag) {
this.linkageFlag = linkageFlag;
}
@Override
protected void encode() {
int i;
int j;
int k;
int data_chars;
final int[] vs = new int[21];
final int[] group = new int[21];
final int[] v_odd = new int[21];
final int[] v_even = new int[21];
final int[][] char_widths = new int[21][8];
int checksum;
int row;
int check_char;
int c_group;
int c_odd;
int c_even;
final int[] check_widths = new int[8];
int pattern_width;
final int[] elements = new int[235];
int codeblocks;
int stack_rows;
int blocksPerRow;
int current_block;
int current_row;
boolean special_case_row;
int elements_in_sub;
int reader;
int writer;
final int[] sub_elements = new int[235];
int l;
int symbol_row;
String separator_pattern;
boolean black;
boolean left_to_right;
int compositeOffset;
this.inputData = toBytes(this.content, StandardCharsets.US_ASCII);
final StringBuilder binaryString = new StringBuilder(this.inputData.length * 8);
if (this.linkageFlag) {
binaryString.append('1');
compositeOffset = 1;
} else {
binaryString.append('0');
compositeOffset = 0;
}
final int encodingMethod = calculateBinaryString(this.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;
}
elements[0] = 1;
elements[1] = 1;
elements[pattern_width - 2] = 1;
elements[pattern_width - 1] = 1;
/* 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 (!this.stacked) {
/* Copy elements into symbol */
this.row_count = 1 + compositeOffset;
this.row_height = new int[1 + compositeOffset];
this.row_height[0 + compositeOffset] = -1;
this.pattern = new String[1 + compositeOffset];
writer = 0;
black = false;
final StringBuilder pat = new StringBuilder("0");
final StringBuilder separator_binary = new StringBuilder();
for (i = 0; i < pattern_width; i++) {
pat.append((char) (elements[i] + '0'));
for (j = 0; j < elements[i]; j++) {
if (black) {
separator_binary.append('0');
} else {
separator_binary.append('1');
}
}
black = !black;
writer += elements[i];
}
this.pattern[0 + compositeOffset] = pat.toString();
separator_binary.setCharAt(0, '0');
separator_binary.setCharAt(1, '0');
separator_binary.setCharAt(2, '0');
separator_binary.setCharAt(3, '0');
separator_binary.delete(writer - 4, separator_binary.length());
for (j = 0; j < writer / 49; j++) {
k = 49 * j + 18;
for (i = 0; i < 15; i++) {
if (separator_binary.charAt(i + k - 1) == '1' && separator_binary.charAt(i + k) == '1') {
separator_binary.setCharAt(i + k, '0');
}
}
}
if (this.linkageFlag) {
// Add composite code separator
this.pattern[0] = bin2pat(separator_binary);
this.row_height[0] = 1;
}
} else {
/* RSS Expanded Stacked */
codeblocks = (data_chars + 1) / 2 + (data_chars + 1) % 2;
blocksPerRow = this.preferredColumns;
if (this.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++;
}
this.row_count = stack_rows * 4 - 3;
this.row_height = new int[this.row_count + compositeOffset];
this.pattern = new String[this.row_count + compositeOffset];
symbol_row = 0;
current_block = 0;
for (current_row = 1; current_row <= stack_rows; current_row++) {
for (i = 0; i < 235; i++) {
sub_elements[i] = 0;
}
special_case_row = false;
/* Row Start */
sub_elements[0] = 1;
sub_elements[1] = 1;
elements_in_sub = 2;
/* Row Data */
reader = 0;
do {
if ((blocksPerRow & 1) != 0 || (current_row & 1) != 0
|| current_row == stack_rows && codeblocks != current_row * blocksPerRow && (current_row * blocksPerRow - codeblocks & 1) != 0) {
/* left to right */
left_to_right = true;
i = 2 + current_block * 21;
for (j = 0; j < 21; j++) {
if (i + j < pattern_width) {
sub_elements[j + reader * 21 + 2] = elements[i + j];
elements_in_sub++;
}
}
} else {
/* right to left */
left_to_right = false;
if (current_row * blocksPerRow < codeblocks) {
/* a full row */
i = 2 + (current_row * blocksPerRow - reader - 1) * 21;
for (j = 0; j < 21; j++) {
if (i + j < pattern_width) {
sub_elements[20 - j + reader * 21 + 2] = elements[i + j];
elements_in_sub++;
}
}
} else {
/* a partial row */
k = current_row * blocksPerRow - codeblocks;
l = current_row * blocksPerRow - reader - 1;
i = 2 + (l - k) * 21;
for (j = 0; j < 21; j++) {
if (i + j < pattern_width) {
sub_elements[20 - j + 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;
StringBuilder pat = new StringBuilder();
this.row_height[symbol_row + compositeOffset] = -1;
if ((current_row & 1) != 0) {
pat.append('0');
black = false;
} else {
if (current_row == stack_rows && codeblocks != current_row * blocksPerRow && (current_row * blocksPerRow - codeblocks & 1) != 0) {
/* Special case bottom row */
special_case_row = true;
sub_elements[0] = 2;
pat.append('0');
black = false;
}
}
writer = 0;
final StringBuilder separator_binary = new StringBuilder();
for (i = 0; i < elements_in_sub; i++) {
pat.append((char) (sub_elements[i] + '0'));
for (j = 0; j < sub_elements[i]; j++) {
separator_binary.append(black ? '0' : '1');
}
black = !black;
writer += sub_elements[i];
}
this.pattern[symbol_row + compositeOffset] = pat.toString();
separator_binary.setCharAt(0, '0');
separator_binary.setCharAt(1, '0');
separator_binary.setCharAt(2, '0');
separator_binary.setCharAt(3, '0');
separator_binary.delete(writer - 4, separator_binary.length());
for (j = 0; j < reader; j++) {
k = 49 * j + (special_case_row ? 19 : 18);
if (left_to_right) {
for (i = 0; i < 15; i++) {
if (separator_binary.charAt(i + k - 1) == '1' && separator_binary.charAt(i + k) == '1') {
separator_binary.setCharAt(i + k, '0');
}
}
} else {
for (i = 14; i >= 0; i--) {
if (separator_binary.charAt(i + k + 1) == '1' && separator_binary.charAt(i + k) == '1') {
separator_binary.setCharAt(i + k, '0');
}
}
}
}
separator_pattern = bin2pat(separator_binary);
if (current_row == 1 && this.linkageFlag) {
// Add composite code separator
this.row_height[0] = 1;
this.pattern[0] = separator_pattern;
}
if (current_row != 1) {
/* middle separator pattern (above current row) */
pat = new StringBuilder("05");
for (j = 5; j < 49 * blocksPerRow; j += 2) {
pat.append("11");
}
this.pattern[symbol_row - 2 + compositeOffset] = pat.toString();
this.row_height[symbol_row - 2 + compositeOffset] = 1;
/* bottom separator pattern (above current row) */
this.row_height[symbol_row - 1 + compositeOffset] = 1;
this.pattern[symbol_row - 1 + compositeOffset] = separator_pattern;
}
if (current_row != stack_rows) {
this.row_height[symbol_row + 1 + compositeOffset] = 1;
this.pattern[symbol_row + 1 + compositeOffset] = separator_pattern;
}
symbol_row += 4;
}
this.readable = "";
this.row_count += compositeOffset;
}
}
/** Handles all data encodation from section 7.2.5 of ISO/IEC 24724. */
private static int calculateBinaryString(final int[] inputData, final 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++) {
final 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++) {
final 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++) {
final int group = parseInt(inputData, i * 3, 3);
binaryAppend(binaryString, group, 10);
}
binaryAppend(binaryString, inputData[19] - '0', 2);
if (encodingMethod == 6) {
final 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++) {
final 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
*/
final int[] generalField = Arrays.copyOfRange(inputData, read_posn, inputData.length);
if (generalField.length != 0) {
latch = false;
final EncodeMode[] generalFieldType = new EncodeMode[generalField.length];
for (i = 0; i < generalField.length; i++) {
/* Tables 11, 12, 13 - ISO/IEC 646 encodation */
final 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;
do {
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++;
}
} while (current_length < generalField.length);
remainder = calculateRemainder(binaryString.length());
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());
/* Now add padding to binary string (7.2.5.5.4) */
i = remainder;
if (generalField.length != 0 && 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(final int binaryStringLength) {
int remainder = 12 - binaryStringLength % 12;
if (remainder == 12) {
remainder = 0;
}
if (binaryStringLength < 36) {
remainder = 36 - binaryStringLength;
}
return remainder;
}
/** Logs binary string as hexadecimal */
private void logBinaryStringInfo(final 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(final EncodeMode[] generalFieldType) {
int block_count, i, j, k;
EncodeMode current, next, last;
final int[] blockLength = new int[200];
final 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(final int[] chars, final int index, final int length) {
int val = 0;
int pow = (int) Math.pow(10, length - 1);
for (int i = 0; i < length; i++) {
final int c = chars[index + i];
val += (c - '0') * pow;
pow /= 10;
}
return val;
}
}