{
* Copyright 2010 ZXing authors
*
* 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.
* Original Authors: Sean Owen
* Delphi Implementation by E. Spelt and K. Gossens
}
unit ZXing.OneD.Code93Reader;
interface
uses
System.SysUtils,
System.Generics.Collections,
Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat,
ZXing.Common.Detector.MathUtils;
type
///
///
Decodes Code 93 barcodes.
///
///
TCode93Reader = class sealed(TOneDReader)
private
class var ALPHABET: TArray;
class function checkChecksums(pResult: TStringBuilder): boolean; static;
class function checkOneChecksum(pResult: TStringBuilder;
checkPosition, weightMax: Integer): boolean;
class function decodeExtended(encoded: TStringBuilder): string; static;
function findAsteriskPattern(row: IBitArray): TArray;
class function patternToChar(pattern: Integer; var c: Char)
: boolean; static;
class function toPattern(counters: TArray): Integer; static;
const
ALPHABET_STRING
: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*';
class var ASTERISK_ENCODING: Integer;
class var
CHARACTER_ENCODINGS: TArray;
counters: TArray;
decodeRowResult: TStringBuilder;
public
constructor Create;
destructor Destroy; override;
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary): TReadResult;
override;
end;
implementation
{ Code93Reader }
constructor TCode93Reader.Create;
begin
ALPHABET := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*'.ToCharArray;
CHARACTER_ENCODINGS := TArray.Create($114, $148, $144, $142, $128,
$124, 290, $150, $112, $10A, $1A8, 420, $1A2, $194, $192, $18A, 360, $164,
$162, $134, $11A, $158, $14C, $146, 300, $116, $1B4, $1B2, $1AC, $1A6, $196,
410, $16C, $166, 310, $13A, $12E, $1D4, $1D2, $1CA, $16E, $176, 430, $126,
$1DA, 470, $132, 350);
ASTERISK_ENCODING := TCode93Reader.CHARACTER_ENCODINGS[$2F];
counters := TArray.Create();
SetLength(counters, 6);
decodeRowResult := TStringBuilder.Create();
end;
destructor TCode93Reader.Destroy;
begin
ALPHABET := nil;
CHARACTER_ENCODINGS := nil;
counters := nil;
FreeAndNil(decodeRowResult);
inherited;
end;
class function TCode93Reader.checkChecksums(pResult: TStringBuilder): boolean;
var
length: Integer;
begin
length := pResult.length;
if (not TCode93Reader.checkOneChecksum(pResult, (length - 2), 20)) then
begin
Result := false;
exit
end;
if (not TCode93Reader.checkOneChecksum(pResult, (length - 1), 15)) then
begin
Result := false;
exit
end;
begin
Result := true;
exit
end
end;
class function TCode93Reader.checkOneChecksum(pResult: TStringBuilder;
checkPosition, weightMax: Integer): boolean;
var
weight, total, i: Integer;
begin
weight := 1;
total := 0;
i := (checkPosition - 1);
while ((i >= 0)) do
begin
inc(total, (weight * ALPHABET_STRING.IndexOf(pResult.Chars[i])));
inc(weight);
if (weight > weightMax) then
weight := 1;
dec(i)
end;
if (pResult.Chars[checkPosition] <> TCode93Reader.ALPHABET[(total mod $2F)])
then
begin
Result := false;
exit
end;
begin
Result := true;
exit
end
end;
class function TCode93Reader.decodeExtended(encoded: TStringBuilder): string;
var
next, c: Char;
decodedChar: Char;
length, i: Integer;
decoded: TStringBuilder;
label Label_0179, Label_0169, Label_014A;
begin
length := encoded.length;
decoded := TStringBuilder.Create(length);
try
i := 0;
next := Char(0);
while ((i < length)) do
begin
c := encoded.Chars[i];
if ((c < 'a') or (c > 'd')) then
goto Label_0179;
if (i < (length - 1)) then
begin
next := encoded.Chars[(i + 1)];
decodedChar := #0;
case c of
'a':
begin
if ((next < 'A') or (next > 'Z')) then
begin
Result := '';
exit
end;
decodedChar := Char(ord(next) - ord('@'));
goto Label_0169
end;
'b':
begin
if ((next < 'A') or (next > 'E')) then
break;;
decodedChar := Char(ord(next) - ord('&'));
goto Label_0169
end;
'c':
begin
if ((next < 'A') or (next > 'O')) then
goto Label_014A;
decodedChar := Char(ord(next) - ord(' '));
goto Label_0169
end;
'd':
begin
if ((next < 'A') or (next > 'Z')) then
begin
Result := '';
exit
end;
decodedChar := Char(ord(next) + ord(' '));
goto Label_0169
end;
else
begin
goto Label_0169
end;
end;
if ((next >= 'F') and (next <= 'W')) then
begin
decodedChar := Char(ord(next) - ord(''));
goto Label_0169
end
end;
begin
Result := '';
exit
end;
Label_014A:
if (next = 'Z') then
decodedChar := ':'
else
begin
Result := '';
exit
end;
Label_0169:
decoded.Append(decodedChar);
inc(i);
continue;
Label_0179:
decoded.Append(c);
inc(i)
end;
Result := decoded.ToString;
finally
FreeAndNil(decoded);
end;
end;
function TCode93Reader.decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary): TReadResult;
var
decodedChar: Char;
lastStart: Integer;
index, nextStart, counter, aEnd, pattern, lastPatternSize: Integer;
start: TArray;
resultString: String;
Left, Right: Single;
resultPointCallback: TResultPointCallback;
obj: TObject;
resultPoints: TArray;
resultPointLeft, resultPointRight: IResultPoint;
begin
for index := 0 to length(counters) - 1 do
counters[index] := 0;
decodeRowResult.length := 0;
start := self.findAsteriskPattern(row);
if (start = nil) then
begin
Result := nil;
exit
end;
nextStart := row.getNextSet(start[1]);
aEnd := row.Size;
repeat
if (not TOneDReader.recordPattern(row, nextStart, self.counters)) then
begin
Result := nil;
exit
end;
pattern := TCode93Reader.toPattern(self.counters);
if (pattern < 0) then
begin
Result := nil;
exit
end;
if (not TCode93Reader.patternToChar(pattern, decodedChar)) then
begin
Result := nil;
exit
end;
self.decodeRowResult.Append(decodedChar);
lastStart := nextStart;
for counter in self.counters do
begin
inc(nextStart, counter)
end;
nextStart := row.getNextSet(nextStart)
until (decodedChar = '*');
self.decodeRowResult.Remove((self.decodeRowResult.length - 1), 1);
lastPatternSize := 0;
for counter in self.counters do
begin
inc(lastPatternSize, counter)
end;
if (not((nextStart <> aEnd) and row[nextStart])) then
begin
Result := nil;
exit
end;
if (self.decodeRowResult.length < 2) then
begin
Result := nil;
exit
end;
if (not TCode93Reader.checkChecksums(self.decodeRowResult)) then
begin
Result := nil;
exit
end;
self.decodeRowResult.length := self.decodeRowResult.length - 2;
resultString := TCode93Reader.decodeExtended(self.decodeRowResult);
if (resultString = '') then
begin
Result := nil;
exit
end;
Left := (start[1] + start[0]) div 2;
Right := (lastStart + lastPatternSize) / 2;
resultPointLeft := TResultPointHelpers.CreateResultPoint(Left, rowNumber);
resultPointRight := TResultPointHelpers.CreateResultPoint(Right, rowNumber);
resultPoints := [resultPointLeft, resultPointRight];
resultPointCallback := nil;
// it is a local variable: it doesn't get NIL as default value, and the following ifs do not assign a value for all possible cases
if ((hints = nil) or
(not hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK))) then
resultPointCallback := nil
else
begin
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
if (obj is TResultPointEventObject) then
resultPointCallback := TResultPointEventObject(obj).Event;
end;
if Assigned(resultPointCallback) then
begin
resultPointCallback(resultPointLeft);
resultPointCallback(resultPointRight);
end;
Result := TReadResult.Create(resultString, nil, resultPoints,
TBarcodeFormat.CODE_93);
end;
function TCode93Reader.findAsteriskPattern(row: IBitArray): TArray;
var
i, l, patternStart, patternLength, width, counterPosition, rowOffset,
index: Integer;
isWhite: boolean;
begin
width := row.Size;
rowOffset := row.getNextSet(0);
index := 0;
while (index < length(self.counters)) do
begin
self.counters[index] := 0;
inc(index)
end;
counterPosition := 0;
patternStart := rowOffset;
isWhite := false;
patternLength := length(counters);
for i := rowOffset to width - 1 do
begin
if (row[i] xor isWhite) then
begin
inc(counters[counterPosition])
end
else
begin
if (counterPosition = (patternLength - 1)) then
begin
if (TCode93Reader.toPattern(counters) = TCode93Reader.ASTERISK_ENCODING)
then
begin
Result := TArray.Create(patternStart, i);
exit
end;
inc(patternStart, (counters[0] + counters[1]));
for l := 2 to patternLength - 2 do
begin
counters[l - 2] := counters[l];
end;
counters[(patternLength - 2)] := 0;
counters[(patternLength - 1)] := 0;
dec(counterPosition)
end
else
inc(counterPosition);
counters[counterPosition] := 1;
isWhite := not isWhite
end;
end;
Result := nil;
end;
class function TCode93Reader.patternToChar(pattern: Integer;
var c: Char): boolean;
var
i: Integer;
begin
i := 0;
while (i < length(TCode93Reader.CHARACTER_ENCODINGS)) do
begin
if (TCode93Reader.CHARACTER_ENCODINGS[i] = pattern) then
begin
c := TCode93Reader.ALPHABET[i];
begin
Result := true;
exit
end
end;
inc(i)
end;
c := '*';
Result := false;
end;
class function TCode93Reader.toPattern(counters: TArray): Integer;
var
counter, max, sum, pattern, i, j, scaledShifted, scaledUnshifted: Integer;
begin
max := length(counters);
sum := 0;
for counter in counters do
begin
inc(sum, counter)
end;
pattern := 0;
i := 0;
while ((i < max)) do
begin
scaledShifted := (((counters[i] shl TOneDReader.INTEGER_MATH_SHIFT) *
9) div sum);
scaledUnshifted := TMathUtils.Asr(scaledShifted,
TOneDReader.INTEGER_MATH_SHIFT);
if ((scaledShifted and $FF) > $7F) then
inc(scaledUnshifted);
if ((scaledUnshifted < 1) or (scaledUnshifted > 4)) then
begin
Result := -1;
exit
end;
if ((i and 1) = 0) then
begin
j := 0;
while ((j < scaledUnshifted)) do
begin
pattern := ((pattern shl 1) or 1);
inc(j)
end
end
else
pattern := (pattern shl scaledUnshifted);
inc(i)
end;
Result := pattern;
end;
end.