{ * 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.