* Characters | Meaning |
* 1-9 | 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). |
* 10-12 | Three-digit country code according to ISO-3166. |
* 13-15 | Three digit service code. This depends on your parcel courier. |
*
*
* @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
public bool encode()
{
inputBytes = Encoding.UTF8.GetBytes(text);
// copy input data over into source
int sourcelen = text.Length;
source = new int[sourcelen];
//eciProcess();
for (int i = 0; i < sourcelen; i++)
{
source[i] = inputBytes[i] & 0xFF;
}
// 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[i] < '0') || (primaryData[i] > '9'))
{
mode = 3;
break;
}
}
}
// initialize the set and character arrays
if (!processText())
{
//error_msg = "Input data too long";
//return false;
throw new Exception("Input data too long");
}
// 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 = new int[character.Length];
character.CopyTo(codewords, 0);
if (mode == 2 || mode == 3)
{
int[] _primary = getPrimaryCodewords();
if (_primary == null)
{
return false;
}
codewords = insert(codewords, 0, _primary);
}
else
{
codewords = insert(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 = insert(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 = codewords.Take(totalMax).ToArray();
int[] __codewords = new int[totalMax];
Array.Copy(codewords, __codewords, totalMax);
codewords = __codewords;
}
// insert primary error correction between primary message and secondary message (always EEC)
//int[] primary = codewords.Take(10).ToArray();
int[] primary = new int[10];
Array.Copy(codewords, 0, primary, 0, 10);
int[] primaryCheck = getErrorCorrection(primary, 10);
codewords = insert(codewords, 10, primaryCheck);
// calculate secondary error correction
//int[] secondary = codewords.Skip(20).ToArray();
int[] secondary = new int[codewords.Length - 20];
Array.Copy(codewords, 20, secondary, 0, secondary.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
int[] _codewords = codewords;
codewords = new int[codewords.Length + secondaryECOdd.Length + secondaryECEven.Length];
Array.Copy(_codewords, codewords, _codewords.Length);
//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];
}
//encodeInfo += "Mode: " + mode + "\n";
//encodeInfo += "ECC Codewords: " + secondaryECMax + "\n";
//encodeInfo += "Codewords: ";
//for (int i = 0; i < codewords.Length; i++)
//{
// encodeInfo += Integer.toString(codewords[i]) + " ";
//}
//encodeInfo += "\n";
// 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.toString());
// row_height[i] = 1;
//}
//symbol_height = 72;
//symbol_width = 74;
plotSymbol();
return true;
}
/**
* 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
*/
int[] getPrimaryCodewords()
{
//assert mode == 2 || mode == 3;
if (primaryData.Length != 15)
{
//error_msg = "Invalid Primary String";
//return null;
throw new Exception("Invalid Primary String");
}
for (int i = 9; i < 15; i++)
{ /* check that country code and service are numeric */
if ((primaryData[i] < '0') || (primaryData[i] > '9'))
{
//error_msg = "Invalid Primary String";
//return null;
throw new Exception("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
{
// if (mode == 3)
postcode = primaryData.Substring(0, 6);
}
int country = int.Parse(primaryData.Substring(9, 3));
int service = int.Parse(primaryData.Substring(12, 3));
//if (debug)
//{
// System.out.println("Using mode " + mode);
// System.out.println(" Postcode: " + postcode);
// System.out.println(" Country Code: " + country);
// System.out.println(" Service: " + service);
//}
if (mode == 2)
{
return getMode2PrimaryCodewords(postcode, country, service);
}
else
{ // 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
*/
static int[] getMode2PrimaryCodewords(String postcode, int country, int service)
{
for (int i = 0; i < postcode.Length; i++)
{
if (postcode[i] < '0' || postcode[i] > '9')
{
postcode = postcode.Substring(0, i);
break;
}
}
int postcodeNum = int.Parse(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
*/
static int[] getMode3PrimaryCodewords(String postcode, int country, int service)
{
int[] postcodeNums = new int[postcode.Length];
postcode = postcode.ToUpper();
for (int i = 0; i < postcodeNums.Length; i++)
{
postcodeNums[i] = postcode[i];
if (postcode[i] >= 'A' && postcode[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
*/
bool processText()
{
int Length = text.Length;
int i, j, count, current_set;
if (Length > 138)
{
return false;
}
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[source[i]];
character[i] = MAXICODE_SYMBOL_CHAR[source[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 Exception("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. */
if ((mode == 2 || mode == 3) && Length > 84)
{
return false;
}
if ((mode == 4 || mode == 6) && Length > 93)
{
return false;
}
if (mode == 5 && Length > 77)
{
return false;
}
return true;
}
/**
* 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
*/
int bestSurroundingSet(int index, int Length, params 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
*/
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
*/
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 };
double[] radii = { 9.91, 6.16, 2.37 };
for (int i = 0; i < radii.Length; i++)
{
target.Add(new Ellipse(
35.76 - radii[i],
35.60 - radii[i],
35.76 + radii[i],
35.60 + radii[i]));
}
}
/** {@inheritDoc} */
//@Override
protected int[] getCodewords()
{
return codewords;
}
//========================================================================
int[] insert(int[] original, int index, int[] inserted)
{
int[] modified = new int[original.Length + inserted.Length];
Array.Copy(original, 0, modified, 0, index);
Array.Copy(inserted, 0, modified, index, inserted.Length);
Array.Copy(original, index, modified, index + inserted.Length, modified.Length - index - inserted.Length);
return modified;
}
bool contains(int[] values, int value)
{
for (int i = 0; i < values.Length; i++)
{
if (values[i] == value)
{
return true;
}
}
return false;
}
}
}