You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
20 KiB
555 lines
20 KiB
/*
|
|
DataMatrix.Net
|
|
|
|
DataMatrix.Net - .net library for decoding DataMatrix codes.
|
|
Copyright (C) 2009/2010 Michael Faschinger
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 3.0 of the License, or (at your option) any later version.
|
|
You can also redistribute and/or modify it under the terms of the
|
|
GNU Lesser General Public License as published by the Free Software
|
|
Foundation; either version 3.0 of the License or (at your option)
|
|
any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License or the GNU Lesser General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public
|
|
License and the GNU Lesser General Public License along with this
|
|
library; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
Contact: Michael Faschinger - michfasch@gmx.at
|
|
|
|
*/
|
|
|
|
using System;
|
|
|
|
namespace DataMatrix.net
|
|
{
|
|
internal class DmtxMessage
|
|
{
|
|
#region Fields
|
|
int _outputIdx; /* Internal index used to store output progress */
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
internal DmtxMessage(DmtxSymbolSize sizeIdx, DmtxFormat symbolFormat)
|
|
{
|
|
if (symbolFormat != DmtxFormat.Matrix && symbolFormat != DmtxFormat.Mosaic)
|
|
{
|
|
throw new ArgumentException("Only DmtxFormats Matrix and Mosaic are currently supported");
|
|
}
|
|
int mappingRows = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribMappingMatrixRows, sizeIdx);
|
|
int mappingCols = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribMappingMatrixCols, sizeIdx);
|
|
|
|
this.Array = new byte[mappingCols * mappingRows];
|
|
|
|
int codeSize = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribSymbolDataWords, sizeIdx) + DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribSymbolErrorWords, sizeIdx);
|
|
this.Code = new byte[codeSize];
|
|
|
|
this.Output = new byte[10 * codeSize];
|
|
}
|
|
#endregion
|
|
|
|
#region Methods
|
|
internal void DecodeDataStream(DmtxSymbolSize sizeIdx, byte[] outputStart)
|
|
{
|
|
bool macro = false;
|
|
|
|
this.Output = outputStart ?? this.Output;
|
|
this._outputIdx = 0;
|
|
|
|
byte[] ptr = this.Code;
|
|
int dataEndIndex = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribSymbolDataWords, sizeIdx);
|
|
|
|
/* Print macro header if first codeword triggers it */
|
|
if (ptr[0] == DmtxConstants.DmtxChar05Macro || ptr[0] == DmtxConstants.DmtxChar06Macro)
|
|
{
|
|
PushOutputMacroHeader(ptr[0]);
|
|
macro = true;
|
|
}
|
|
|
|
for (int codeIter = 0; codeIter < dataEndIndex; )
|
|
{
|
|
|
|
DmtxScheme encScheme = GetEncodationScheme(this.Code[codeIter]);
|
|
if (encScheme != DmtxScheme.DmtxSchemeAscii)
|
|
codeIter++;
|
|
|
|
switch (encScheme)
|
|
{
|
|
case DmtxScheme.DmtxSchemeAscii:
|
|
codeIter = DecodeSchemeAscii(codeIter, dataEndIndex);
|
|
break;
|
|
case DmtxScheme.DmtxSchemeC40:
|
|
case DmtxScheme.DmtxSchemeText:
|
|
codeIter = DecodeSchemeC40Text(codeIter, dataEndIndex, encScheme);
|
|
break;
|
|
case DmtxScheme.DmtxSchemeX12:
|
|
codeIter = DecodeSchemeX12(codeIter, dataEndIndex);
|
|
break;
|
|
case DmtxScheme.DmtxSchemeEdifact:
|
|
codeIter = DecodeSchemeEdifact(codeIter, dataEndIndex);
|
|
break;
|
|
case DmtxScheme.DmtxSchemeBase256:
|
|
codeIter = DecodeSchemeBase256(codeIter, dataEndIndex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Print macro trailer if required */
|
|
if (macro)
|
|
{
|
|
PushOutputMacroTrailer();
|
|
}
|
|
}
|
|
|
|
private void PushOutputMacroHeader(byte macroType)
|
|
{
|
|
PushOutputWord((byte)'[');
|
|
PushOutputWord((byte)')');
|
|
PushOutputWord((byte)'>');
|
|
PushOutputWord(30); /* ASCII RS */
|
|
PushOutputWord((byte)'0');
|
|
|
|
if (macroType == DmtxConstants.DmtxChar05Macro)
|
|
{
|
|
PushOutputWord((byte)'5');
|
|
}
|
|
else if (macroType == DmtxConstants.DmtxChar06Macro)
|
|
{
|
|
PushOutputWord((byte)'6');
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException("Macro Header only supported for char05 and char06");
|
|
}
|
|
|
|
PushOutputWord(29); /* ASCII GS */
|
|
}
|
|
|
|
void PushOutputMacroTrailer()
|
|
{
|
|
PushOutputWord(30); /* ASCII RS */
|
|
PushOutputWord(4); /* ASCII EOT */
|
|
}
|
|
|
|
void PushOutputWord(byte value)
|
|
{
|
|
this.Output[this._outputIdx++] = value;
|
|
}
|
|
|
|
static DmtxScheme GetEncodationScheme(byte val)
|
|
{
|
|
if (val == DmtxConstants.DmtxCharC40Latch)
|
|
{
|
|
return DmtxScheme.DmtxSchemeC40;
|
|
}
|
|
if (val == DmtxConstants.DmtxCharBase256Latch)
|
|
{
|
|
return DmtxScheme.DmtxSchemeBase256;
|
|
}
|
|
if (val == DmtxConstants.DmtxCharEdifactLatch)
|
|
{
|
|
return DmtxScheme.DmtxSchemeEdifact;
|
|
}
|
|
if (val == DmtxConstants.DmtxCharTextLatch)
|
|
{
|
|
return DmtxScheme.DmtxSchemeText;
|
|
}
|
|
if (val == DmtxConstants.DmtxCharX12Latch)
|
|
{
|
|
return DmtxScheme.DmtxSchemeX12;
|
|
}
|
|
return DmtxScheme.DmtxSchemeAscii;
|
|
}
|
|
|
|
int DecodeSchemeAscii(int startIndex, int endIndex)
|
|
{
|
|
bool upperShift = false;
|
|
|
|
while (startIndex < endIndex)
|
|
{
|
|
|
|
byte codeword = this.Code[startIndex];
|
|
|
|
if (GetEncodationScheme(this.Code[startIndex]) != DmtxScheme.DmtxSchemeAscii)
|
|
return startIndex;
|
|
|
|
startIndex++;
|
|
|
|
if (upperShift)
|
|
{
|
|
PushOutputWord((byte)(codeword + 127));
|
|
upperShift = false;
|
|
}
|
|
else if (codeword == DmtxConstants.DmtxCharAsciiUpperShift)
|
|
{
|
|
upperShift = true;
|
|
}
|
|
else if (codeword == DmtxConstants.DmtxCharAsciiPad)
|
|
{
|
|
this.PadCount = endIndex - startIndex;
|
|
return endIndex;
|
|
}
|
|
else if (codeword <= 128)
|
|
{
|
|
PushOutputWord((byte)(codeword - 1));
|
|
}
|
|
else if (codeword <= 229)
|
|
{
|
|
byte digits = (byte)(codeword - 130);
|
|
PushOutputWord((byte)(digits / 10 + '0'));
|
|
PushOutputWord((byte)(digits - (digits / 10) * 10 + '0'));
|
|
}
|
|
}
|
|
|
|
return startIndex;
|
|
}
|
|
|
|
int DecodeSchemeC40Text(int startIndex, int endIndex, DmtxScheme encScheme)
|
|
{
|
|
int[] c40Values = new int[3];
|
|
C40TextState state = new C40TextState {Shift = DmtxConstants.DmtxC40TextBasicSet, UpperShift = false};
|
|
|
|
|
|
if (!(encScheme == DmtxScheme.DmtxSchemeC40 || encScheme == DmtxScheme.DmtxSchemeText))
|
|
{
|
|
throw new ArgumentException("Invalid scheme selected for decodind!");
|
|
}
|
|
|
|
while (startIndex < endIndex)
|
|
{
|
|
|
|
/* FIXME Also check that ptr+1 is safe to access */
|
|
int packed = (this.Code[startIndex] << 8) | this.Code[startIndex + 1];
|
|
c40Values[0] = ((packed - 1) / 1600);
|
|
c40Values[1] = ((packed - 1) / 40) % 40;
|
|
c40Values[2] = (packed - 1) % 40;
|
|
startIndex += 2;
|
|
|
|
int i;
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (state.Shift == DmtxConstants.DmtxC40TextBasicSet)
|
|
{ /* Basic set */
|
|
if (c40Values[i] <= 2)
|
|
{
|
|
state.Shift = c40Values[i] + 1;
|
|
}
|
|
else if (c40Values[i] == 3)
|
|
{
|
|
PushOutputC40TextWord(ref state, ' ');
|
|
}
|
|
else if (c40Values[i] <= 13)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] - 13 + '9'); /* 0-9 */
|
|
}
|
|
else if (c40Values[i] <= 39)
|
|
{
|
|
if (encScheme == DmtxScheme.DmtxSchemeC40)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] - 39 + 'Z'); /* A-Z */
|
|
}
|
|
else if (encScheme == DmtxScheme.DmtxSchemeText)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] - 39 + 'z'); /* a-z */
|
|
}
|
|
}
|
|
}
|
|
else if (state.Shift == DmtxConstants.DmtxC40TextShift1)
|
|
{ /* Shift 1 set */
|
|
PushOutputC40TextWord(ref state, c40Values[i]); /* ASCII 0 - 31 */
|
|
}
|
|
else if (state.Shift == DmtxConstants.DmtxC40TextShift2)
|
|
{ /* Shift 2 set */
|
|
if (c40Values[i] <= 14)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] + 33); /* ASCII 33 - 47 */
|
|
}
|
|
else if (c40Values[i] <= 21)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] + 43); /* ASCII 58 - 64 */
|
|
}
|
|
else if (c40Values[i] <= 26)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] + 69); /* ASCII 91 - 95 */
|
|
}
|
|
else if (c40Values[i] == 27)
|
|
{
|
|
PushOutputC40TextWord(ref state, 0x1d); /* FNC1 -- XXX depends on position? */
|
|
}
|
|
else if (c40Values[i] == 30)
|
|
{
|
|
state.UpperShift = true;
|
|
state.Shift = DmtxConstants.DmtxC40TextBasicSet;
|
|
}
|
|
}
|
|
else if (state.Shift == DmtxConstants.DmtxC40TextShift3)
|
|
{ /* Shift 3 set */
|
|
if (encScheme == DmtxScheme.DmtxSchemeC40)
|
|
{
|
|
PushOutputC40TextWord(ref state, c40Values[i] + 96);
|
|
}
|
|
else if (encScheme == DmtxScheme.DmtxSchemeText)
|
|
{
|
|
if (c40Values[i] == 0)
|
|
PushOutputC40TextWord(ref state, c40Values[i] + 96);
|
|
else if (c40Values[i] <= 26)
|
|
PushOutputC40TextWord(ref state, c40Values[i] - 26 + 'Z'); /* A-Z */
|
|
else
|
|
PushOutputC40TextWord(ref state, c40Values[i] - 31 + 127); /* { | } ~ DEL */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Unlatch if codeword 254 follows 2 codewords in C40/Text encodation */
|
|
if (this.Code[startIndex] == DmtxConstants.DmtxCharTripletUnlatch)
|
|
return startIndex + 1;
|
|
|
|
/* Unlatch is implied if only one codeword remains */
|
|
if (endIndex - startIndex == 1)
|
|
return startIndex;
|
|
}
|
|
return startIndex;
|
|
}
|
|
|
|
void PushOutputC40TextWord(ref C40TextState state, int value)
|
|
{
|
|
if (!(value >= 0 && value < 256))
|
|
{
|
|
throw new ArgumentException("Invalid value: Exceeds range for conversion to byte");
|
|
}
|
|
|
|
this.Output[this._outputIdx] = (byte)value;
|
|
|
|
if (state.UpperShift)
|
|
{
|
|
if (!(value >= 0 && value < 256))
|
|
{
|
|
throw new ArgumentException("Invalid value: Exceeds range for conversion to upper case character");
|
|
}
|
|
this.Output[this._outputIdx] += 128;
|
|
}
|
|
|
|
this._outputIdx++;
|
|
|
|
state.Shift = DmtxConstants.DmtxC40TextBasicSet;
|
|
state.UpperShift = false;
|
|
}
|
|
|
|
private int DecodeSchemeX12(int startIndex, int endIndex)
|
|
{
|
|
int[] x12Values = new int[3];
|
|
|
|
while (startIndex < endIndex)
|
|
{
|
|
|
|
/* FIXME Also check that ptr+1 is safe to access */
|
|
int packed = (this.Code[startIndex] << 8) | this.Code[startIndex + 1];
|
|
x12Values[0] = ((packed - 1) / 1600);
|
|
x12Values[1] = ((packed - 1) / 40) % 40;
|
|
x12Values[2] = (packed - 1) % 40;
|
|
startIndex += 2;
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
if (x12Values[i] == 0)
|
|
PushOutputWord(13);
|
|
else if (x12Values[i] == 1)
|
|
PushOutputWord(42);
|
|
else if (x12Values[i] == 2)
|
|
PushOutputWord(62);
|
|
else if (x12Values[i] == 3)
|
|
PushOutputWord(32);
|
|
else if (x12Values[i] <= 13)
|
|
PushOutputWord((byte)(x12Values[i] + 44));
|
|
else if (x12Values[i] <= 90)
|
|
PushOutputWord((byte)(x12Values[i] + 51));
|
|
}
|
|
|
|
/* Unlatch if codeword 254 follows 2 codewords in C40/Text encodation */
|
|
if (this.Code[startIndex] == DmtxConstants.DmtxCharTripletUnlatch)
|
|
return startIndex + 1;
|
|
|
|
/* Unlatch is implied if only one codeword remains */
|
|
if (endIndex - startIndex == 1)
|
|
return startIndex;
|
|
}
|
|
|
|
return startIndex;
|
|
}
|
|
|
|
int DecodeSchemeEdifact(int startIndex, int endIndex)
|
|
{
|
|
byte[] unpacked = new byte[4];
|
|
|
|
while (startIndex < endIndex)
|
|
{
|
|
|
|
/* FIXME Also check that ptr+2 is safe to access -- shouldn't be a
|
|
problem because I'm guessing you can guarantee there will always
|
|
be at least 3 error codewords */
|
|
unpacked[0] = (byte)((this.Code[startIndex] & 0xfc) >> 2);
|
|
unpacked[1] = (byte)((this.Code[startIndex] & 0x03) << 4 | (this.Code[startIndex + 1] & 0xf0) >> 4);
|
|
unpacked[2] = (byte)((this.Code[startIndex + 1] & 0x0f) << 2 | (this.Code[startIndex + 2] & 0xc0) >> 6);
|
|
unpacked[3] = (byte)(this.Code[startIndex + 2] & 0x3f);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
|
|
/* Advance input ptr (4th value comes from already-read 3rd byte) */
|
|
if (i < 3)
|
|
startIndex++;
|
|
|
|
/* Test for unlatch condition */
|
|
if (unpacked[i] == DmtxConstants.DmtxCharEdifactUnlatch)
|
|
{
|
|
if (this.Output[_outputIdx] != 0)
|
|
{/* XXX dirty why? */
|
|
throw new Exception("Error decoding edifact scheme");
|
|
}
|
|
return startIndex;
|
|
}
|
|
|
|
PushOutputWord((byte)(unpacked[i] ^ (((unpacked[i] & 0x20) ^ 0x20) << 1)));
|
|
}
|
|
|
|
/* Unlatch is implied if fewer than 3 codewords remain */
|
|
if (endIndex - startIndex < 3)
|
|
{
|
|
return startIndex;
|
|
}
|
|
}
|
|
return startIndex;
|
|
}
|
|
|
|
int DecodeSchemeBase256(int startIndex, int endIndex)
|
|
{
|
|
int tempEndIndex;
|
|
|
|
/* Find positional index used for unrandomizing */
|
|
int idx = startIndex + 1;
|
|
|
|
int d0 = UnRandomize255State(this.Code[startIndex++], idx++);
|
|
if (d0 == 0)
|
|
{
|
|
tempEndIndex = endIndex;
|
|
}
|
|
else if (d0 <= 249)
|
|
{
|
|
tempEndIndex = startIndex + d0;
|
|
}
|
|
else
|
|
{
|
|
int d1 = UnRandomize255State(this.Code[startIndex++], idx++);
|
|
tempEndIndex = startIndex + (d0 - 249) * 250 + d1;
|
|
}
|
|
|
|
if (tempEndIndex > endIndex)
|
|
{
|
|
throw new Exception("Error decoding scheme base 256");
|
|
}
|
|
|
|
while (startIndex < tempEndIndex)
|
|
{
|
|
PushOutputWord(UnRandomize255State(this.Code[startIndex++], idx++));
|
|
}
|
|
|
|
return startIndex;
|
|
}
|
|
|
|
internal static byte UnRandomize255State(byte value, int idx)
|
|
{
|
|
int pseudoRandom = ((149 * idx) % 255) + 1;
|
|
int tmp = value - pseudoRandom;
|
|
if (tmp < 0)
|
|
tmp += 256;
|
|
|
|
if (tmp < 0 || tmp >= 256)
|
|
{
|
|
throw new Exception("Error unrandomizing 255 state");
|
|
}
|
|
|
|
return (byte)tmp;
|
|
}
|
|
|
|
internal int SymbolModuleStatus(DmtxSymbolSize sizeIdx, int symbolRow, int symbolCol)
|
|
{
|
|
int dataRegionRows = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribDataRegionRows, sizeIdx);
|
|
int dataRegionCols = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribDataRegionCols, sizeIdx);
|
|
int symbolRows = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribSymbolRows, sizeIdx);
|
|
int mappingCols = DmtxCommon.GetSymbolAttribute(DmtxSymAttribute.DmtxSymAttribMappingMatrixCols, sizeIdx);
|
|
|
|
int symbolRowReverse = symbolRows - symbolRow - 1;
|
|
int mappingRow = symbolRowReverse - 1 - 2 * (symbolRowReverse / (dataRegionRows + 2));
|
|
int mappingCol = symbolCol - 1 - 2 * (symbolCol / (dataRegionCols + 2));
|
|
|
|
/* Solid portion of alignment patterns */
|
|
if (symbolRow % (dataRegionRows + 2) == 0 || symbolCol % (dataRegionCols + 2) == 0)
|
|
{
|
|
return (DmtxConstants.DmtxModuleOnRGB | (DmtxConstants.DmtxModuleData == 0 ? 1 : 0)); // check if unary not is correct
|
|
}
|
|
|
|
/* Horinzontal calibration bars */
|
|
if ((symbolRow + 1) % (dataRegionRows + 2) == 0)
|
|
{
|
|
return (((symbolCol & 0x01) != 0) ? 0 : DmtxConstants.DmtxModuleOnRGB) | (DmtxConstants.DmtxModuleData == 0 ? 1 : 0);
|
|
}
|
|
|
|
/* Vertical calibration bars */
|
|
if ((symbolCol + 1) % (dataRegionCols + 2) == 0)
|
|
{
|
|
return (((symbolRow & 0x01) != 0) ? 0 : DmtxConstants.DmtxModuleOnRGB) | (DmtxConstants.DmtxModuleData == 0 ? 1 : 0);
|
|
}
|
|
|
|
/* Data modules */
|
|
return (this.Array[mappingRow * mappingCols + mappingCol] | DmtxConstants.DmtxModuleData);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
internal int PadCount { get; set; }
|
|
|
|
internal byte[] Array { get; set; }
|
|
|
|
internal byte[] Code { get; set; }
|
|
|
|
internal byte[] Output { get; set; }
|
|
|
|
internal int ArraySize
|
|
{
|
|
get
|
|
{
|
|
return this.Array.Length;
|
|
}
|
|
}
|
|
|
|
internal int CodeSize
|
|
{
|
|
get
|
|
{
|
|
return this.Code.Length;
|
|
}
|
|
}
|
|
|
|
internal int OutputSize
|
|
{
|
|
get
|
|
{
|
|
return this.Output.Length;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|