/*
* 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.HumanReadableLocation.NONE;
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;
import java.awt.geom.Rectangle2D;
/**
* Implements UPC bar code symbology
* According to BS EN 797:1996
* <br>
* UPC-A requires an 11 digit article number. The check digit is calculated.
* UPC-E is a zero-compressed version of UPC-A developed for smaller packages.
* The code requires a 6 digit article number (digits 0-9). The check digit
* is calculated. Also supports Number System 1 encoding by entering a 7-digit
* article number stating with the digit 1. In addition EAN-2 and EAN-5 add-on
* symbols can be added using the + character followed by the add-on data.
*
* @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
*/
public class Upc extends Symbol {
public enum Mode {
UPCA, UPCE
};
private boolean useAddOn;
private String addOnContent;
private Mode mode;
private boolean linkageFlag;
private String[] setAC = {
"3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312",
"1213", "3112"
};
private String[] setB = {
"1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131",
"3121", "2113"
};
private String[] UPCParity0 = {
"BBBAAA", "BBABAA", "BBAABA", "BBAAAB", "BABBAA", "BAABBA", "BAAABB",
"BABABA", "BABAAB", "BAABAB"
}; /* Number set for UPC-E symbol (EN Table 4) */
private String[] UPCParity1 = {
"AAABBB", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA",
"ABABAB", "ABABBA", "ABBABA"
}; /* Not covered by BS EN 797 */
public Upc() {
mode = Mode.UPCA;
linkageFlag = false;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public Mode getMode() {
return mode;
}
public void setLinkageFlag() {
linkageFlag = true;
}
public void unsetLinkageFlag() {
linkageFlag = false;
}
@Override
public void setHumanReadableLocation(HumanReadableLocation humanReadableLocation) {
if (humanReadableLocation == TOP) {
throw new IllegalArgumentException("Cannot display human-readable text above UPC bar codes.");
} else {
super.setHumanReadableLocation(humanReadableLocation);
}
}
@Override
public boolean encode() {
boolean retval;
AddOn addOn = new AddOn();
String addOnData = "";
separateContent();
if(content.length() == 0) {
error_msg = "Missing UPC data";
retval = false;
} else {
if (mode == Mode.UPCA) {
retval = upca();
} else {
retval = upce();
}
}
if (useAddOn) {
addOnData = addOn.calcAddOn(addOnContent);
if (addOnData.length() == 0) {
error_msg = "Invalid Add-On data";
retval = false;
} else {
pattern[0] = pattern[0] + "9" + addOnData;
//add leading zeroes to add-on text
if(addOnContent.length() == 1) {
addOnContent = "0" + addOnContent;
}
if(addOnContent.length() == 3) {
addOnContent = "0" + addOnContent;
}
if(addOnContent.length() == 4) {
addOnContent = "0" + addOnContent;
}
}
}
if (retval) {
plotSymbol();
}
return retval;
}
private void separateContent() {
int splitPoint;
splitPoint = content.indexOf('+');
if(splitPoint != -1) {
// There is a '+' in the input data, use an add-on EAN2 or EAN5
useAddOn = true;
addOnContent = content.substring(splitPoint + 1);
content = content.substring(0, splitPoint);
if(debug) {
System.out.println("Content: " + content);
System.out.println("Addon: " + addOnContent);
}
}
}
private boolean upca() {
String accumulator;
String dest;
int i;
char check;
if (!(content.matches("[0-9]+"))) {
error_msg = "Invalid characters in input";
return false;
}
if (content.length() > 11) {
error_msg = "Input data too long";
return false;
}
accumulator = "";
for (i = content.length(); i < 11; i++) {
accumulator += "0";
}
accumulator += content;
check = calcDigit(accumulator);
accumulator += check;
dest = "111";
for (i = 0; i < 12; i++) {
if (i == 6) {
dest += "11111";
}
dest += setAC[Character.getNumericValue(accumulator.charAt(i))];
}
dest += "111";
encodeInfo += "Check Digit: " + check + "\n";
readable = accumulator;
pattern = new String[1];
pattern[0] = dest;
row_count = 1;
row_height = new int[1];
row_height[0] = -1;
return true;
}
private boolean upce() {
int i, num_system;
char emode, check;
String source, parity, dest;
char[] equivalent = new char[12];
String equiv = "";
if (!(content.matches("[0-9]+"))) {
error_msg = "Invalid characters in input";
return false;
}
if (content.length() > 7) {
error_msg = "Input data too long";
return false;
}
source = "";
for (i = content.length(); i < 7; i++) {
source += "0";
}
source += content;
/* Two number systems can be used - system 0 and system 1 */
switch (source.charAt(0)) {
case '0':
num_system = 0;
break;
case '1':
num_system = 1;
break;
default:
error_msg = "Invalid input data";
return false;
}
/* Expand the zero-compressed UPCE code to make a UPCA equivalent (EN Table 5) */
emode = source.charAt(6);
for (i = 0; i < 11; i++) {
equivalent[i] = '0';
}
equivalent[0] = source.charAt(0);
equivalent[1] = source.charAt(1);
equivalent[2] = source.charAt(2);
switch (emode) {
case '0':
case '1':
case '2':
equivalent[3] = emode;
equivalent[8] = source.charAt(3);
equivalent[9] = source.charAt(4);
equivalent[10] = source.charAt(5);
break;
case '3':
equivalent[3] = source.charAt(3);
equivalent[9] = source.charAt(4);
equivalent[10] = source.charAt(5);
if (((source.charAt(3) == '0') || (source.charAt(3) == '1'))
|| (source.charAt(3) == '2')) {
/* Note 1 - "X3 shall not be equal to 0, 1 or 2" */
error_msg = "Invalid UPC-E data";
return false;
}
break;
case '4':
equivalent[3] = source.charAt(3);
equivalent[4] = source.charAt(4);
equivalent[10] = source.charAt(5);
if (source.charAt(4) == '0') {
/* Note 2 - "X4 shall not be equal to 0" */
error_msg = "Invalid UPC-E data";
return false;
}
break;
case '5':
case '6':
case '7':
case '8':
case '9':
equivalent[3] = source.charAt(3);
equivalent[4] = source.charAt(4);
equivalent[5] = source.charAt(5);
equivalent[10] = emode;
if (source.charAt(5) == '0') {
/* Note 3 - "X5 shall not be equal to 0" */
error_msg = "Invalid UPC-E data";
return false;
}
break;
}
for (i = 0; i < 11; i++) {
equiv += equivalent[i];
}
/* Get the check digit from the expanded UPCA code */
check = calcDigit(equiv);
encodeInfo += "Check Digit: " + check + "\n";
/* Use the number system and check digit information to choose a parity scheme */
if (num_system == 1) {
parity = UPCParity1[check - '0'];
} else {
parity = UPCParity0[check - '0'];
}
/* Take all this information and make the barcode pattern */
/* start character */
dest = "111";
for (i = 0; i <= 5; i++) {
switch (parity.charAt(i)) {
case 'A':
dest += setAC[source.charAt(i + 1) - '0'];
break;
case 'B':
dest += setB[source.charAt(i + 1) - '0'];
break;
}
}
/* stop character */
dest += "111111";
readable = source + check;
pattern = new String[1];
pattern[0] = dest;
row_count = 1;
row_height = new int[1];
row_height[0] = -1;
return true;
}
private char calcDigit(String x) {
int count = 0;
int c, cdigit;
for (int i = 0; i < 11; i++) {
c = Character.getNumericValue(x.charAt(i));
if ((i % 2) == 0) {
c = c * 3;
}
count = count + c;
}
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;
int compositeOffset = 0;
int shortLongDiff = 5;
rectangles.clear();
texts.clear();
black = true;
x = 0;
if (linkageFlag) {
compositeOffset = 6;
}
for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) {
if (black) {
y = 0;
black = false;
w = pattern[0].charAt(xBlock) - '0';
h = default_height;
/* Add extension to guide bars */
if (mode == Mode.UPCA) {
if ((x < 10) || (x > 84)) {
h += shortLongDiff;
}
if ((x > 45) && (x < 49)) {
h += shortLongDiff;
}
if (x > 95) {
// Drop add-on
h -= 8;
y = 8;
}
if (linkageFlag && (x == 0) || (x == 94)) {
h += 2;
y -= 2;
}
} else {
if ((x < 4) || (x > 45)) {
h += shortLongDiff;
}
if (x > 52) {
// Drop add-on
h -= 8;
y = 8;
}
if (linkageFlag && (x == 0) || (x == 50)) {
h += 2;
y -= 2;
}
}
Rectangle2D.Double rect = new Rectangle2D.Double(x + 6, y + compositeOffset, w, h);
rectangles.add(rect);
if ((x + w + 12) > symbol_width) {
symbol_width = x + w + 12;
}
} else {
black = true;
}
x += pattern[0].charAt(xBlock) - '0';
}
if (linkageFlag) {
// Add separator for composite symbology
if (mode == Mode.UPCA) {
rectangles.add(new Rectangle2D.Double(0 + 6, 0, 1, 2));
rectangles.add(new Rectangle2D.Double(94 + 6, 0, 1, 2));
rectangles.add(new Rectangle2D.Double(-1 + 6, 2, 1, 2));
rectangles.add(new Rectangle2D.Double(95 + 6, 2, 1, 2));
} else { // UPCE
rectangles.add(new Rectangle2D.Double(0 + 6, 0, 1, 2));
rectangles.add(new Rectangle2D.Double(50 + 6, 0, 1, 2));
rectangles.add(new Rectangle2D.Double(-1 + 6, 2, 1, 2));
rectangles.add(new Rectangle2D.Double(51 + 6, 2, 1, 2));
}
}
symbol_height = default_height + 5;
/* Now add the text */
if (humanReadableLocation != NONE) {
double baseline = getHeight() + fontSize - shortLongDiff + compositeOffset;
double addOnBaseline = 6.0 + compositeOffset;
if (mode == Mode.UPCA) {
texts.add(new TextBox(3, baseline, readable.substring(0, 1)));
texts.add(new TextBox(34, baseline, readable.substring(1, 6)));
texts.add(new TextBox(73, baseline, readable.substring(6, 11)));
texts.add(new TextBox(104, baseline, readable.substring(11, 12)));
if (useAddOn) {
if (addOnContent.length() == 2) {
texts.add(new TextBox(118, addOnBaseline, addOnContent));
} else {
texts.add(new TextBox(133, addOnBaseline, addOnContent));
}
}
} else { // UPCE
texts.add(new TextBox(3, baseline, readable.substring(0, 1)));
texts.add(new TextBox(30, baseline, readable.substring(1, 7)));
texts.add(new TextBox(61, baseline, readable.substring(7, 8)));
if (useAddOn) {
if (addOnContent.length() == 2) {
texts.add(new TextBox(75, addOnBaseline, addOnContent));
} else {
texts.add(new TextBox(90, addOnBaseline, addOnContent));
}
}
}
}
}
}