/*
* 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 EAN bar code symbology
* According to BS EN 797:1996
* <br>
* European Article Number data can be encoded in EAN-8 or EAN-13 format
* requiring a 7-digit or 12-digit input respectively. EAN-13 numbers map to
* Global Trade Identification Numbers (GTIN) whereas EAN-8 symbols are
* generally for internal use only. Check digit is calculated and should not
* be in input data. Leading zeroes are added as required.
*
* @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
*/
public class Ean extends Symbol {
public enum Mode {
EAN8, EAN13
};
private boolean useAddOn;
private String addOnContent;
private Mode mode;
private boolean linkageFlag;
private String[] EAN13Parity = {
"AAAAAA", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA",
"ABABAB", "ABABBA", "ABBABA"
};
private String[] EANsetA = {
"3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213",
"3112"
};
private String[] EANsetB = {
"1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121",
"2113"
};
public Ean() {
mode = Mode.EAN13;
useAddOn = false;
addOnContent = "";
linkageFlag = false;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public Mode getMode() {
return mode;
}
protected void setLinkageFlag() {
linkageFlag = true;
}
protected void unsetLinkageFlag() {
linkageFlag = false;
}
@Override
public void setHumanReadableLocation(HumanReadableLocation humanReadableLocation) {
if (humanReadableLocation == TOP) {
throw new IllegalArgumentException("Cannot display human-readable text above EAN bar codes.");
} else {
super.setHumanReadableLocation(humanReadableLocation);
}
}
@Override
public boolean encode() {
boolean retval = false;
AddOn addOn = new AddOn();
String addOnData = "";
separateContent();
if(content.length() == 0) {
error_msg = "Missing EAN data";
retval = false;
} else {
switch (mode) {
case EAN8:
retval = ean8();
break;
case EAN13:
retval = ean13();
break;
}
}
if ((retval) && (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 ean13() {
String accumulator = "";
String dest, parity;
int i;
if (!(content.matches("[0-9]+"))) {
error_msg = "Invalid characters in input";
return false;
}
if (content.length() > 12) {
error_msg = "Input data too long";
return false;
}
for (i = content.length(); i < 12; i++) {
accumulator += "0";
}
accumulator += content;
accumulator += calcDigit(accumulator);
parity = EAN13Parity[accumulator.charAt(0) - '0'];
encodeInfo += "Parity Digit: " + accumulator.charAt(0) + "\n";
/* Start character */
dest = "111";
for (i = 1; i < 13; i++) {
if (i == 7) {
dest += "11111";
}
if ((i >= 1) && (i <= 6)) {
if (parity.charAt(i - 1) == 'B') {
dest += EANsetB[accumulator.charAt(i) - '0'];
} else {
dest += EANsetA[accumulator.charAt(i) - '0'];
}
} else {
dest += EANsetA[accumulator.charAt(i) - '0'];
}
}
dest += "111";
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 ean8() {
String accumulator = "";
int i;
String dest;
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;
}
for (i = content.length(); i < 7; i++) {
accumulator += "0";
}
accumulator += content;
accumulator += calcDigit(accumulator);
dest = "111";
for (i = 0; i < 8; i++) {
if (i == 4) {
dest += "11111";
}
dest += EANsetA[Character.getNumericValue(accumulator.charAt(i))];
}
dest += "111";
readable = accumulator;
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;
int p = 0;
for (int i = x.length() - 1; i >= 0; i--) {
c = Character.getNumericValue(x.charAt(i));
if ((p % 2) == 0) {
c = c * 3;
}
count += c;
p++;
}
cdigit = 10 - (count % 10);
if (cdigit == 10) {
cdigit = 0;
}
encodeInfo += "Check Digit: " + cdigit + "\n";
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.EAN13) {
if ((x < 3) || (x > 91)) {
h += shortLongDiff;
}
if ((x > 45) && (x < 49)) {
h += shortLongDiff;
}
if (x > 95) {
// Drop add-on
h -= 8;
y = 8;
}
if (linkageFlag) {
if ((x == 0) || (x == 94)) {
h += 2;
y -= 2;
}
}
}
if (mode == Mode.EAN8) {
if ((x < 3) || (x > 62)) {
h += shortLongDiff;
}
if ((x > 30) && (x < 35)) {
h += shortLongDiff;
}
if (x > 66) {
// Drop add-on
h -= 8;
y = 8;
}
if (linkageFlag) {
if ((x == 0) || (x == 66)) {
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 += (double)(pattern[0].charAt(xBlock) - '0');
}
if (linkageFlag) {
// Add separator for composite symbology
if (mode == Mode.EAN13) {
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 {
rectangles.add(new Rectangle2D.Double(0 + 6, 0, 1, 2));
rectangles.add(new Rectangle2D.Double(66 + 6, 0, 1, 2));
rectangles.add(new Rectangle2D.Double(-1 + 6, 2, 1, 2));
rectangles.add(new Rectangle2D.Double(67 + 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.EAN13) {
texts.add(new TextBox(3, baseline, readable.substring(0, 1)));
texts.add(new TextBox(30, baseline, readable.substring(1, 7)));
texts.add(new TextBox(77, baseline, readable.substring(7, 13)));
if (useAddOn) {
if (addOnContent.length() == 2) {
texts.add(new TextBox(118, addOnBaseline, addOnContent));
} else {
texts.add(new TextBox(133, addOnBaseline, addOnContent));
}
}
} else { // EAN8
texts.add(new TextBox(23, baseline, readable.substring(0, 4)));
texts.add(new TextBox(55, baseline, readable.substring(4, 8)));
if (useAddOn) {
if (addOnContent.length() == 2) {
texts.add(new TextBox(93, addOnBaseline, addOnContent));
} else {
texts.add(new TextBox(105, addOnBaseline, addOnContent));
}
}
}
}
}
}