feat(apk): scanner le QR du poste depuis l'app (ZXing.Delphi, Apache 2.0)

Le scanner du systeme demandait avec quoi ouvrir le lien. L'app ouvre la
camera arriere, decode une image sur quatre et remplit URL + jeton des
qu'un lien pmserver://sync est lu. Rien n'est enregistre avant Save.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-17 18:38:41 +01:00
parent 780eecc11b
commit dd9fd62dcb
81 changed files with 22141 additions and 19 deletions
@@ -0,0 +1,696 @@
{
* Copyright 2008 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.Code128Reader;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.Helpers,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat;
var
CODE_PATTERNS: TArray<TArray<Integer>>;
type
/// <summary>
/// <p>Decodes Code 128 barcodes.</p>
/// </summary>
TCode128Reader = class(TOneDReader)
private const
CODE_SHIFT = 98;
CODE_CODE_C = 99;
CODE_CODE_B = 100;
CODE_CODE_A = 101;
CODE_FNC_1 = 102;
CODE_FNC_2 = 97;
CODE_FNC_3 = 96;
CODE_FNC_4_A = 101;
CODE_FNC_4_B = 100;
CODE_START_A = 103;
CODE_START_B = 104;
CODE_START_C = 105;
CODE_STOP = 106;
class var MAX_AVG_VARIANCE: Integer;
class var MAX_INDIVIDUAL_VARIANCE: Integer;
class function FindStartPattern(row: IBitArray): TArray<Integer>;
class function DecodeCode(row: IBitArray; counters: TArray<Integer>;
rowOffset: Integer; var code: Integer): Boolean;
class procedure DoInitialize();
class procedure DoFinalize();
public
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
override;
end;
implementation
class procedure TCode128Reader.DoFinalize;
begin
CODE_PATTERNS := nil;
end;
class procedure TCode128Reader.DoInitialize;
begin
CODE_PATTERNS := [[2, 1, 2, 2, 2, 2], // 0
[2, 2, 2, 1, 2, 2], [2, 2, 2, 2, 2, 1], [1, 2, 1, 2, 2, 3],
[1, 2, 1, 3, 2, 2], [1, 3, 1, 2, 2, 2], // 5
[1, 2, 2, 2, 1, 3], [1, 2, 2, 3, 1, 2], [1, 3, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 3], [2, 2, 1, 3, 1, 2], // 10
[2, 3, 1, 2, 1, 2], [1, 1, 2, 2, 3, 2], [1, 2, 2, 1, 3, 2],
[1, 2, 2, 2, 3, 1], [1, 1, 3, 2, 2, 2], // 15
[1, 2, 3, 1, 2, 2], [1, 2, 3, 2, 2, 1], [2, 2, 3, 2, 1, 1],
[2, 2, 1, 1, 3, 2], [2, 2, 1, 2, 3, 1], // 20
[2, 1, 3, 2, 1, 2], [2, 2, 3, 1, 1, 2], [3, 1, 2, 1, 3, 1],
[3, 1, 1, 2, 2, 2], [3, 2, 1, 1, 2, 2], // 25
[3, 2, 1, 2, 2, 1], [3, 1, 2, 2, 1, 2], [3, 2, 2, 1, 1, 2],
[3, 2, 2, 2, 1, 1], [2, 1, 2, 1, 2, 3], // 30
[2, 1, 2, 3, 2, 1], [2, 3, 2, 1, 2, 1], [1, 1, 1, 3, 2, 3],
[1, 3, 1, 1, 2, 3], [1, 3, 1, 3, 2, 1], // 35
[1, 1, 2, 3, 1, 3], [1, 3, 2, 1, 1, 3], [1, 3, 2, 3, 1, 1],
[2, 1, 1, 3, 1, 3], [2, 3, 1, 1, 1, 3], // 40
[2, 3, 1, 3, 1, 1], [1, 1, 2, 1, 3, 3], [1, 1, 2, 3, 3, 1],
[1, 3, 2, 1, 3, 1], [1, 1, 3, 1, 2, 3], // 45
[1, 1, 3, 3, 2, 1], [1, 3, 3, 1, 2, 1], [3, 1, 3, 1, 2, 1],
[2, 1, 1, 3, 3, 1], [2, 3, 1, 1, 3, 1], // 50
[2, 1, 3, 1, 1, 3], [2, 1, 3, 3, 1, 1], [2, 1, 3, 1, 3, 1],
[3, 1, 1, 1, 2, 3], [3, 1, 1, 3, 2, 1], // 55
[3, 3, 1, 1, 2, 1], [3, 1, 2, 1, 1, 3], [3, 1, 2, 3, 1, 1],
[3, 3, 2, 1, 1, 1], [3, 1, 4, 1, 1, 1], // 60
[2, 2, 1, 4, 1, 1], [4, 3, 1, 1, 1, 1], [1, 1, 1, 2, 2, 4],
[1, 1, 1, 4, 2, 2], [1, 2, 1, 1, 2, 4], // 65
[1, 2, 1, 4, 2, 1], [1, 4, 1, 1, 2, 2], [1, 4, 1, 2, 2, 1],
[1, 1, 2, 2, 1, 4], [1, 1, 2, 4, 1, 2], // 70
[1, 2, 2, 1, 1, 4], [1, 2, 2, 4, 1, 1], [1, 4, 2, 1, 1, 2],
[1, 4, 2, 2, 1, 1], [2, 4, 1, 2, 1, 1], // 75
[2, 2, 1, 1, 1, 4], [4, 1, 3, 1, 1, 1], [2, 4, 1, 1, 1, 2],
[1, 3, 4, 1, 1, 1], [1, 1, 1, 2, 4, 2], // 80
[1, 2, 1, 1, 4, 2], [1, 2, 1, 2, 4, 1], [1, 1, 4, 2, 1, 2],
[1, 2, 4, 1, 1, 2], [1, 2, 4, 2, 1, 1], // 85
[4, 1, 1, 2, 1, 2], [4, 2, 1, 1, 1, 2], [4, 2, 1, 2, 1, 1],
[2, 1, 2, 1, 4, 1], [2, 1, 4, 1, 2, 1], // 90
[4, 1, 2, 1, 2, 1], [1, 1, 1, 1, 4, 3], [1, 1, 1, 3, 4, 1],
[1, 3, 1, 1, 4, 1], [1, 1, 4, 1, 1, 3], // 95
[1, 1, 4, 3, 1, 1], [4, 1, 1, 1, 1, 3], [4, 1, 1, 3, 1, 1],
[1, 1, 3, 1, 4, 1], [1, 1, 4, 1, 3, 1], // 100
[3, 1, 1, 1, 4, 1], [4, 1, 1, 1, 3, 1], [2, 1, 1, 4, 1, 2],
[2, 1, 1, 2, 1, 4], [2, 1, 1, 2, 3, 2], // 105
[2, 3, 3, 1, 1, 1, 2]];
MAX_AVG_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.25);
MAX_INDIVIDUAL_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7);
end;
class function TCode128Reader.FindStartPattern(row: IBitArray): TArray<Integer>;
var
i, bestMatch, startCode, bestVariance, width, rowOffset, patternStart,
patternLength, variance, counterPosition: Integer;
counters: TArray<Integer>;
isWhite: Boolean;
begin
width := row.Size;
rowOffset := row.getNextSet(0);
counterPosition := 0;
counters := TArray<Integer>.Create();
SetLength(counters, 6);
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
bestVariance := MAX_AVG_VARIANCE;
bestMatch := -1;
startCode := CODE_START_A;
while startCode <= CODE_START_C do
begin
variance := patternMatchVariance(counters, CODE_PATTERNS[startCode],
MAX_INDIVIDUAL_VARIANCE);
if variance < bestVariance then
begin
bestVariance := variance;
bestMatch := startCode
end;
inc(startCode);
end;
if bestMatch >= 0 then
begin
// Look for whitespace before start pattern, >= 50% of width of start pattern
if row.isRange(Max(0, patternStart - (i - patternStart) div 2),
patternStart, false) then
begin
result := [patternStart, i, bestMatch];
exit;
end
end;
patternStart := patternStart + counters[0] + counters[1];
// Array.Copy(counters, 2, counters, 0, patternLength - 2);
// source, source index, dest, dest index, lengte
counters := TArray.CopyInSameArray(counters, 2, patternLength - 2);
counters[patternLength - 2] := 0;
counters[patternLength - 1] := 0;
dec(counterPosition);
end
else
begin
inc(counterPosition);
end;
counters[counterPosition] := 1;
isWhite := not isWhite
end;
end;
result := nil;
end;
class function TCode128Reader.DecodeCode(row: IBitArray;
counters: TArray<Integer>; rowOffset: Integer; var code: Integer): Boolean;
var
bestVariance, l, d, variance: Integer;
pattern: TArray<Integer>;
begin
code := -1;
if not recordPattern(row, rowOffset, counters) then
begin
result := false;
exit;
end;
bestVariance := MAX_AVG_VARIANCE;
// worst variance we'll accept
l := Length(CODE_PATTERNS) - 1;
for d := 0 to l do
begin
pattern := CODE_PATTERNS[d];
variance := patternMatchVariance(counters, pattern,
MAX_INDIVIDUAL_VARIANCE);
if variance < bestVariance then
begin
bestVariance := variance;
code := d
end;
end;
// TODO We're overlooking the fact that the STOP pattern has 7 values, not 6.
result := code >= 0;
end;
function TCode128Reader.decodeRow(const rowNumber: Integer;
const row: IBitArray; const hints: TDictionary<TDecodeHintType, TObject>)
: TReadResult;
var
shiftUpperMode, upperMode, lastCharacterWasPrintable, convertFNC1, done,
unshift, isNextShifted: Boolean;
counters, startPatternInfo: TArray<Integer>;
lastPatternSize, multiplier, checksumTotal, code, lastCode, nextStart,
lastStart, startCode, codeSet, l, rawCodesSize: Integer;
rawCodes: TList<Byte>;
rawBytes: TArray<Byte>;
aResult: String;
counter, resultLength, i: Integer;
left, right: Single;
resultPointCallback: TResultPointCallback;
obj: TObject;
resultPoints: TArray<IResultPoint>;
resultPointLeft, resultPointRight: IResultPoint;
begin
convertFNC1 := (hints <> nil) and
(hints.ContainsKey(ZXing.DecodeHintType.ASSUME_GS1));
startPatternInfo := FindStartPattern(row);
if (startPatternInfo = nil) then
begin
result := nil;
exit;
end;
startCode := startPatternInfo[2];
rawCodes := TList<Byte>.Create();
try
rawCodes.Add(Byte(startCode));
case startCode of
CODE_START_A:
begin
codeSet := CODE_CODE_A;
end;
CODE_START_B:
begin
codeSet := CODE_CODE_B;
end;
CODE_START_C:
begin
codeSet := CODE_CODE_C;
end
else
result := nil;
exit;
end;
done := false;
isNextShifted := false;
lastStart := startPatternInfo[0];
nextStart := startPatternInfo[1];
SetLength(counters, 6);
lastCode := 0;
code := 0;
checksumTotal := startCode;
multiplier := 0;
lastCharacterWasPrintable := true;
upperMode := false;
shiftUpperMode := false;
while not done do
begin
unshift := isNextShifted;
isNextShifted := false;
// Save off last code
lastCode := code;
// Decode another code from image
if not DecodeCode(row, counters, nextStart, code) then
begin
result := nil;
exit;
end;
rawCodes.Add(Byte(code));
// Remember whether the last code was printable or not (excluding CODE_STOP)
if code <> CODE_STOP then
begin
lastCharacterWasPrintable := true
end;
// Add to checksum computation (if not CODE_STOP of course)
if code <> CODE_STOP then
begin
inc(multiplier);
checksumTotal := checksumTotal + (multiplier * code);
end;
// Advance to where the next code will to start
lastStart := nextStart;
l := Length(counters) - 1;
for counter := 0 to l do
begin
nextStart := nextStart + counters[counter];
end;
// Take care of illegal start codes
case code of
CODE_START_A, CODE_START_B, CODE_START_C:
begin
result := nil;
exit;
end;
end;
case codeSet of
CODE_CODE_A:
begin
if code < 64 then
begin
if shiftUpperMode = upperMode then
begin
aResult := aResult + Char(32 + code);
end
else
begin
aResult := aResult + Char(32 + 128)
end;
shiftUpperMode := false
end
else if code < 96 then
begin
if shiftUpperMode = upperMode then
begin
aResult := aResult + Char(code - 64)
end
else
begin
aResult := aResult + Char(code + 64)
end;
shiftUpperMode := false
end
else
begin
// Don't let CODE_STOP, which always appears, affect whether whether we think the last
// code was printable or not.
if code <> CODE_STOP then
begin
lastCharacterWasPrintable := false
end;
case code of
CODE_FNC_1:
begin
if convertFNC1 then
begin
if Length(aResult) = 0 then
begin
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
// is FNC1 then this is GS1-128. We add the symbology identifier.
aResult := aResult + ']C1'
end
else
begin
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
aResult := aResult + Char(29)
end
end;
end;
CODE_FNC_2, CODE_FNC_3:
begin
// do nothing?
end;
CODE_FNC_4_A:
begin
if (not upperMode) and (shiftUpperMode) then
begin
upperMode := true;
shiftUpperMode := false
end
else if (upperMode) and (shiftUpperMode) then
begin
upperMode := false;
shiftUpperMode := false
end
else
begin
shiftUpperMode := true
end;
end;
CODE_SHIFT:
begin
isNextShifted := true;
codeSet := CODE_CODE_B;
end;
CODE_CODE_B:
begin
codeSet := CODE_CODE_B;
end;
CODE_CODE_C:
begin
codeSet := CODE_CODE_C;
end;
CODE_STOP:
begin
done := true;
end;
end
end;
end;
CODE_CODE_B:
begin
if code < 96 then
begin
if shiftUpperMode = upperMode then
begin
aResult := aResult + Char(32 + code)
end
else
begin
aResult := aResult + Char(32 + code + 128)
end;
shiftUpperMode := false
end
else
begin
if code <> CODE_STOP then
begin
lastCharacterWasPrintable := false
end;
case code of
CODE_FNC_1:
begin
if convertFNC1 then
begin
if Length(aResult) = 0 then
begin
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
// is FNC1 then this is GS1-128. We add the symbology identifier.
aResult := aResult + (']C1')
end
else
begin
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
aResult := aResult + Char(29)
end
end;
end;
CODE_FNC_2, CODE_FNC_3:
begin
// do nothing?
end;
CODE_FNC_4_B:
begin
if (not upperMode) and (shiftUpperMode) then
begin
upperMode := true;
shiftUpperMode := false
end
else if (upperMode) and (shiftUpperMode) then
begin
upperMode := false;
shiftUpperMode := false
end
else
begin
shiftUpperMode := true
end;
end;
CODE_SHIFT:
begin
isNextShifted := true;
codeSet := CODE_CODE_A;
end;
CODE_CODE_A:
begin
codeSet := CODE_CODE_A;
end;
CODE_CODE_C:
begin
codeSet := CODE_CODE_C;
end;
CODE_STOP:
begin
done := true;
end;
end
end;
end;
CODE_CODE_C:
begin
if code < 100 then
begin
if code < 10 then
begin
aResult := aResult + ('0')
end;
aResult := aResult + IntToStr(code);
end
else
begin
if code <> CODE_STOP then
begin
lastCharacterWasPrintable := false
end;
case code of
CODE_FNC_1:
begin
if convertFNC1 then
begin
if Length(aResult) = 0 then
begin
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
// is FNC1 then this is GS1-128. We add the symbology identifier.
aResult := aResult + (']C1')
end
else
begin
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
aResult := aResult + Char(29)
end
end;
end;
CODE_CODE_A:
begin
codeSet := CODE_CODE_A;
end;
CODE_CODE_B:
begin
codeSet := CODE_CODE_B;
end;
CODE_STOP:
begin
done := true;
end;
end
end;
end;
end;
// Unshift back to another code set if we were shifted
if (unshift) then
begin
if (codeSet = CODE_CODE_A) then
codeSet := CODE_CODE_B
else
codeSet := CODE_CODE_A;
end;
end;
lastPatternSize := nextStart - lastStart;
// Check for ample whitespace following pattern, but, to do this we first need to remember that
// we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left
// to read off. Would be slightly better to properly read. Here we just skip it:
nextStart := row.getNextUnset(nextStart);
if not row.isRange(nextStart, System.Math.Min(row.Size,
nextStart + (nextStart - lastStart) div 2), false) then
begin
result := nil;
exit;
end;
// Pull out from sum the value of the penultimate check code
checksumTotal := checksumTotal - (multiplier * lastCode);
// lastCode is the checksum then:
if checksumTotal mod 103 <> lastCode then
begin
result := nil;
exit;
end;
// Need to pull out the check digits from string
resultLength := Length(aResult);
if resultLength = 0 then
begin
// false positive
result := nil;
exit;
end;
// Only bother if the result had at least one character, and if the checksum digit happened to
// be a printable character. If it was just interpreted as a control code, nothing to remove.
if (resultLength > 0) and (lastCharacterWasPrintable) then
begin
if codeSet = CODE_CODE_C then
begin
aResult := aResult.Remove(resultLength - 2, 2);
end
else
begin
aResult := aResult.Remove(resultLength - 1, 1);
end
end;
left := (startPatternInfo[1] + startPatternInfo[0]) / 2;
right := lastStart + lastPatternSize / 2;
rawCodesSize := rawCodes.Count;
SetLength(rawBytes, rawCodesSize);
for i := 0 to rawCodesSize - 1 do
rawBytes[i] := rawCodes[i];
resultPointLeft := TResultPointHelpers.CreateResultPoint(left, rowNumber);
resultPointRight := TResultPointHelpers.CreateResultPoint(right, rowNumber);
resultPoints := [resultPointLeft, resultPointRight];
resultPointCallback := nil;
// had to add this initialization because the following if/else construct does not assign a value
// for all possible cases. Being resultPointCallback a local variable it is not initialized by default to null,
// so you could get unpredictable results
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;
finally
FreeAndNil(rawCodes);
end;
result := TReadResult.Create(aResult, rawBytes, resultPoints,
TBarcodeFormat.CODE_128);
end;
initialization
TCode128Reader.DoInitialize;
finalization
TCode128Reader.DoFinalize;
end.
@@ -0,0 +1,471 @@
unit ZXing.OneD.Code39Reader;
{
* Copyright 2008 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
* Implemented by Nano103 and E. Spelt for Delphi
}
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
TCode39Reader = class sealed(TOneDReader)
private
class var ALPHABET: TArray<Char>;
class function decodeExtended(encoded: string): string; static;
function findAsteriskPattern(row: IBitArray): TArray<Integer>;
class function patternToChar(pattern: Integer; var c: Char)
: boolean; static;
class function toNarrowWidePattern(counters: TArray<Integer>)
: Integer; static;
const
ALPHABET_STRING: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%';
class var ASTERISK_ENCODING: Integer;
class var
CHARACTER_ENCODINGS: TArray<Integer>;
counters: TArray<Integer>;
decodeRowResult: TStringBuilder;
usingCheckDigit: boolean;
extendedMode: boolean;
public
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
override;
constructor Create(AUsingCheckDigit, AExtendedMode: boolean);
destructor Destroy(); override;
end;
implementation
{ Code93Reader }
constructor TCode39Reader.Create(AUsingCheckDigit, AExtendedMode: boolean);
begin
ALPHABET := ALPHABET_STRING.ToCharArray;
CHARACTER_ENCODINGS := TArray<Integer>.Create($034, $121, $061, $160, $031,
$130, $070, $025, $124, $064, // 0-9
$109, $049, $148, $019, $118, $058, $00D, $10C, $04C, $01C, // A-J
$103, $043, $142, $013, $112, $052, $007, $106, $046, $016, // K-T
$181, $0C1, $1C0, $091, $190, $0D0, $085, $184, $0C4, $094, // U-*
$0A8, $0A2, $08A, $02A // $-%
);
ASTERISK_ENCODING := TCode39Reader.CHARACTER_ENCODINGS[$27];
counters := TArray<Integer>.Create();
SetLength(counters, 9);
decodeRowResult := TStringBuilder.Create();
usingCheckDigit := AUsingCheckDigit;
extendedMode := AExtendedMode;
end;
destructor TCode39Reader.Destroy;
begin
ALPHABET := nil;
CHARACTER_ENCODINGS := nil;
counters := nil;
FreeAndNil(decodeRowResult);
inherited;
end;
class function TCode39Reader.decodeExtended(encoded: string): string;
var
next, c, decodedChar: Char;
length, i: Integer;
decoded: TStringBuilder;
begin
length := encoded.length;
decoded := TStringBuilder.Create(length);
try
i := 0;
while i < length do
begin
c := encoded.Chars[i];
if (((c = '+') or (c = '$')) or ((c = '%') or (c = '/'))) then
begin
if ((i + 1) >= encoded.length) then
begin
Result := '';
exit
end;
next := encoded.Chars[(i + 1)];
decodedChar := Char(#0);
case c of
'+':
begin
if ((next >= 'A') and (next <= 'Z')) then
begin
decodedChar := Char(ord(next) + 32);
end
else
begin
exit('');
end
end;
'$':
begin
if ((next >= 'A') and (next <= 'Z')) then
begin
decodedChar := Char(ord(next) - 64);
end
else
begin
exit('');
end
end;
'%':
begin
if ((next >= 'A') and (next <= 'E')) then
decodedChar := Char(ord(next) - 38)
else if ((next >= 'F') and (next <= 'W')) then
decodedChar := Char(ord(next) - 11)
else
begin
exit('');
end;
end;
'/':
begin
if ((next >= 'A') and (next <= 'O')) then
begin
decodedChar := Char(ord(next) - 32);
end
else if (next = 'Z') then
begin
decodedChar := ':';
end
else
exit('');
end;
end;
decoded.Append(decodedChar);
Inc(i);
end
else
decoded.Append(c);
Inc(i);
end; // end loop/while
Result := decoded.ToString;
finally
decoded.Free;
end;
end;
function TCode39Reader.decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
var
decodedChar: Char;
lastStart: Integer;
index, nextStart, counter, aEnd, pattern, lastPatternSize: Integer;
start: TArray<Integer>;
resultString: String;
Left, Right: Single;
resultPoints: TArray<IResultPoint>;
resultPointLeft, resultPointRight: IResultPoint;
whiteSpaceAfterEnd: Integer;
i, max, total: Integer;
begin
for index := 0 to length(counters) - 1 do
begin
counters[index] := 0;
end;
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 := TCode39Reader.toNarrowWidePattern(self.counters);
if (pattern < 0) then
begin
Result := nil;
exit
end;
if (not TCode39Reader.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;
whiteSpaceAfterEnd := nextStart - lastStart - lastPatternSize;
if (nextStart <> aEnd) and ((whiteSpaceAfterEnd shl 1) < lastPatternSize) then
begin
Result := nil;
exit
end;
if (usingCheckDigit) then
begin
max := self.decodeRowResult.length - 1;
total := 0;
for i := 0 to max - 1 do
begin
Inc(total, ALPHABET_STRING.indexOf(decodeRowResult.Chars[i]));
end;
if (self.decodeRowResult.Chars[max] <> ALPHABET_STRING[total mod 43]) then
begin
Result := nil;
exit
end;
self.decodeRowResult.Remove(max, self.decodeRowResult.length);
end;
if (self.decodeRowResult.length = 0) then
begin
Result := nil;
exit
end;
if extendedMode then
resultString := TCode39Reader.decodeExtended
(self.decodeRowResult.ToString())
else
resultString := decodeRowResult.ToString();
if (resultString = '') then
begin
Result := nil;
exit
end;
Left := (start[1] + start[0]) / 2;
Right := lastStart + (lastPatternSize / 2);
resultPointLeft := TResultPointHelpers.CreateResultPoint(Left, rowNumber);
resultPointRight := TResultPointHelpers.CreateResultPoint(Right, rowNumber);
resultPoints := [resultPointLeft, resultPointRight];
Result := TReadResult.Create(resultString, nil, resultPoints,
TBarcodeFormat.CODE_39);
end;
function TCode39Reader.findAsteriskPattern(row: IBitArray): TArray<Integer>;
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 (TCode39Reader.toNarrowWidePattern(counters)
= TCode39Reader.ASTERISK_ENCODING) then
begin
Result := TArray<Integer>.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 TCode39Reader.patternToChar(pattern: Integer;
var c: Char): boolean;
var
i: Integer;
begin
i := 0;
while (i < length(TCode39Reader.CHARACTER_ENCODINGS)) do
begin
if (TCode39Reader.CHARACTER_ENCODINGS[i] = pattern) then
begin
c := TCode39Reader.ALPHABET[i];
begin
Result := true;
exit
end
end;
Inc(i)
end;
c := '*';
Result := False;
end;
class function TCode39Reader.toNarrowWidePattern
(counters: TArray<Integer>): Integer;
var
numCounters: Integer;
maxNarrowCounter: Integer;
wideCounters: Integer;
minCounter: Integer;
counter: Integer;
totalWideCountersWidth: Integer;
pattern: Integer;
i: Integer;
begin
numCounters := length(counters);
maxNarrowCounter := 0;
repeat
minCounter := High(Integer);
for counter in counters do
begin
if (counter < minCounter) and (counter > maxNarrowCounter) then
minCounter := counter;
end;
maxNarrowCounter := minCounter;
wideCounters := 0;
totalWideCountersWidth := 0;
pattern := 0;
for i := 0 to numCounters - 1 do
begin
counter := counters[i];
if (counter > maxNarrowCounter) then
begin
pattern := pattern or (1 shl (numCounters - 1 - i));
Inc(wideCounters);
Inc(totalWideCountersWidth, counter);
end;
end;
if (wideCounters = 3) then
begin
// Found 3 wide counters, but are they close enough in width?
// We can perform a cheap, conservative check to see if any individual
// counter is more than 1.5 times the average:
i := 0;
while (i < numCounters) and (wideCounters > 0) do
begin
counter := counters[i];
if (counter > maxNarrowCounter) then
begin
dec(wideCounters);
// totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average
if ((counter * 2) >= totalWideCountersWidth) then
begin
Result := -1;
exit;
end;
end;
Inc(i);
end;
Result := pattern;
exit;
end;
until (wideCounters <= 3);
Result := -1;
end;
end.
@@ -0,0 +1,523 @@
{
* 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
/// <summary>
/// <p>Decodes Code 93 barcodes.</p>
/// <see cref="TCode39Reader" />
/// </summary>
TCode93Reader = class sealed(TOneDReader)
private
class var ALPHABET: TArray<Char>;
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<Integer>;
class function patternToChar(pattern: Integer; var c: Char)
: boolean; static;
class function toPattern(counters: TArray<Integer>): Integer; static;
const
ALPHABET_STRING
: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*';
class var ASTERISK_ENCODING: Integer;
class var
CHARACTER_ENCODINGS: TArray<Integer>;
counters: TArray<Integer>;
decodeRowResult: TStringBuilder;
public
constructor Create;
destructor Destroy; override;
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
override;
end;
implementation
{ Code93Reader }
constructor TCode93Reader.Create;
begin
ALPHABET := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*'.ToCharArray;
CHARACTER_ENCODINGS := TArray<Integer>.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<Integer>.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<TDecodeHintType, TObject>): TReadResult;
var
decodedChar: Char;
lastStart: Integer;
index, nextStart, counter, aEnd, pattern, lastPatternSize: Integer;
start: TArray<Integer>;
resultString: String;
Left, Right: Single;
resultPointCallback: TResultPointCallback;
obj: TObject;
resultPoints: TArray<IResultPoint>;
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<Integer>;
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<Integer>.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>): 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.
@@ -0,0 +1,219 @@
{
* Copyright 2008 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: dswitkin@google.com (Daniel Switkin), Sean Owen and
* alasdair@google.com (Alasdair Mackintosh)
* Delphi Implementation by K. Gossens, E. Spelt
}
unit ZXing.OneD.EAN13Reader;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.OneD.UPCEANReader,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat;
type
/// <summary>
/// <p>Implements decoding of the EAN-13 format.</p>
/// </summary>
TEAN13Reader = class(TUPCEANReader)
private
// For an EAN-13 barcode, the first digit is represented by the parities used
// to encode the next six digits, according to the table below. For example,
// if the barcode is 5 123456 789012 then the value of the first digit is
// signified by using odd for '1', even for '2', even for '3', odd for '4',
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
//
// Parity of next 6 digits
// Digit 0 1 2 3 4 5
// 0 Odd Odd Odd Odd Odd Odd
// 1 Odd Odd Even Odd Even Even
// 2 Odd Odd Even Even Odd Even
// 3 Odd Odd Even Even Even Odd
// 4 Odd Even Odd Odd Even Even
// 5 Odd Even Even Odd Odd Even
// 6 Odd Even Even Even Odd Odd
// 7 Odd Even Odd Even Odd Even
// 8 Odd Even Odd Even Even Odd
// 9 Odd Even Even Odd Even Odd
//
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
//
// The encoding is represented by the following array, which is a bit pattern
// using Odd = 0 and Even = 1. For example, 5 is represented by:
//
// Odd Even Even Odd Odd Even
// in binary:
// 0 1 1 0 0 1 == 0x19
//
class var
FIRST_DIGIT_ENCODINGS: TArray<Integer>;
decodeMiddleCounters: TArray<Integer>;
/// <summary>
/// Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded
/// digits in a barcode, determines the implicitly encoded first digit and adds it to the
/// result string.
/// </summary>
/// <param name="resultString">string to insert decoded first digit into</param>
/// <param name="lgPatternFound">int whose bits indicates the pattern of odd/even L/G patterns used to</param>
/// encode digits
/// <return>-1 if first digit cannot be determined</return>
class function determineFirstDigit(const resultString: TStringBuilder;
const lgPatternFound: Integer): Boolean; static;
class procedure InitializeClass; static;
class procedure FinalizeClass; static;
protected
public
/// <summary>
/// Subclasses override this to decode the portion of a barcode between the start
/// and end guard patterns.
/// </summary>
/// <param name="row">row of black/white values to search</param>
/// <param name="startRange">start/end offset of start guard pattern</param>
/// <param name="resultString"><see cref="StringBuilder"/>to append decoded chars to</param>
/// <returns>
/// horizontal offset of first pixel after the "middle" that was decoded or -1 if decoding could not complete successfully
/// </returns>
class function DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const resultString: TStringBuilder)
: Integer; override;
/// <summary>
/// Get the format of this decoder.
/// <returns>The 1D format.</returns>
/// </summary>
function BarcodeFormat: TBarcodeFormat; override;
end;
implementation
{ TEAN13Reader }
class function TEAN13Reader.DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>;
const resultString: TStringBuilder): Integer;
var
bestMatch: Integer;
counter: Integer;
ending: Integer;
counters, middleRange: TArray<Integer>;
rowOffset, x, lgPatternFound: Integer;
begin
Result := -1;
counters := decodeMiddleCounters;
counters[0] := 0;
counters[1] := 0;
counters[2] := 0;
counters[3] := 0;
ending := row.Size;
rowOffset := startRange[1];
lgPatternFound := 0;
x := 0;
while ((x < 6) and (rowOffset < ending)) do
begin
if (not decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS, bestMatch))
then
exit;
resultString.Append( IntToStr ( bestMatch mod 10) );
for counter in counters do
Inc(rowOffset, counter);
if (bestMatch >= 10) then
lgPatternFound := lgPatternFound or (1 shl (5 - x));
Inc(x);
end;
if (not determineFirstDigit(resultString, lgPatternFound)) then
exit;
middleRange := findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
if (middleRange = nil) then
exit;
rowOffset := middleRange[1];
x := 0;
while ((x < 6) and (rowOffset < ending)) do
begin
if (not decodeDigit(row, counters, rowOffset, L_PATTERNS, bestMatch)) then
exit;
resultString.Append(IntToStr(bestMatch));
for counter in counters do
Inc(rowOffset, counter);
Inc(x);
end;
Result := rowOffset;
end;
function TEAN13Reader.BarcodeFormat: TBarcodeFormat;
begin
Result := TBarcodeFormat.EAN_13;
end;
class function TEAN13Reader.determineFirstDigit(const resultString
: TStringBuilder; const lgPatternFound: Integer): Boolean;
var
d: Integer;
begin
Result := false;
for d := 0 to Pred(10) do
begin
if (lgPatternFound = FIRST_DIGIT_ENCODINGS[d]) then
begin
resultString.Insert(0, IntToStr(d));
Result := true;
break;
end;
end;
end;
class procedure TEAN13Reader.InitializeClass;
begin
FIRST_DIGIT_ENCODINGS := TArray<Integer>.Create($00, $0B, $0D, $0E, $13, $19,
$1C, $15, $16, $1A);
SetLength(decodeMiddleCounters, 4);
end;
class procedure TEAN13Reader.FinalizeClass;
begin
FIRST_DIGIT_ENCODINGS := nil;
decodeMiddleCounters := nil;
end;
initialization
TEAN13Reader.InitializeClass;
finalization
TEAN13Reader.FinalizeClass;
end.
@@ -0,0 +1,148 @@
{
* Copyright 2008 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: dswitkin@google.com (Daniel Switkin), Sean Owen and
* alasdair@google.com (Alasdair Mackintosh)
*
* Delphi Implementation by E. Spelt
}
unit ZXing.OneD.EAN8Reader;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.OneD.UPCEANReader,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat;
type
/// <summary>
/// <p>Implements decoding of the EAN-8 format.</p>
/// </summary>
TEAN8Reader = class(TUPCEANReader)
private
class var DecodeMiddleCounters: TArray<Integer>;
class procedure DoInitialize();
class procedure DoFinalize();
public
class function DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const resultString: TStringBuilder)
: Integer; override;
function BarcodeFormat: TBarcodeFormat; override;
end;
implementation
function TEAN8Reader.BarcodeFormat: TBarcodeFormat;
begin
result := TBarcodeFormat.EAN_8;
end;
class procedure TEAN8Reader.DoFinalize;
begin
DecodeMiddleCounters := nil;
end;
class procedure TEAN8Reader.DoInitialize;
begin
SetLength(DecodeMiddleCounters, 4);
end;
class function TEAN8Reader.DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>;
const resultString: TStringBuilder): Integer;
var
ending, rowOffset, x, bestMatch: Integer;
counter: Integer;
counters, middleRange: TArray<Integer>;
begin
counters := self.DecodeMiddleCounters;
counters[0] := 0;
counters[1] := 0;
counters[2] := 0;
counters[3] := 0;
ending := row.Size;
rowOffset := startRange[1];
x := 0;
while (((x < 4) and (rowOffset < ending))) do
begin
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset,
TUPCEANReader.L_PATTERNS, bestMatch)) then
begin
result := -1;
exit
end;
resultString.Append(IntToStr(bestMatch));
for counter in counters do
begin
inc(rowOffset, counter)
end;
inc(x)
end;
middleRange := TUPCEANReader.findGuardPattern(row, rowOffset, true,
TUPCEANReader.MIDDLE_PATTERN);
if (middleRange = nil) then
begin
result := -1;
exit
end;
rowOffset := middleRange[1];
x := 0;
while (((x < 4) and (rowOffset < ending))) do
begin
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset,
TUPCEANReader.L_PATTERNS, bestMatch)) then
begin
result := -1;
exit
end;
resultString.Append(IntToStr(bestMatch));
for counter in counters do
begin
inc(rowOffset, counter)
end;
inc(x)
end;
result := rowOffset;
end;
initialization
TEAN8Reader.DoInitialize;
finalization
TEAN8Reader.DoFinalize;
end.
@@ -0,0 +1,230 @@
{
* 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 Author: Sean Owen
* Delphi Implementation by K. Gossens
}
unit ZXing.OneD.EANManufacturerOrgSupport;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
System.Math,
ZXing.OneD.UPCEANExtension2Support,
ZXing.OneD.UPCEANExtension5Support,
ZXing.Reader,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultMetadataType,
ZXing.ResultPoint,
ZXing.Common.BitArray,
ZXing.Common.Detector.MathUtils;
type
// <summary>
/// Records EAN prefix to GS1 Member Organization, where the member organization
/// correlates strongly with a country. This is an imperfect means of identifying
/// a country of origin by EAN-13 barcode value. See
/// <a href="http://en.wikipedia.org/wiki/List_of_GS1_country_codes">
/// http://en.wikipedia.org/wiki/List_of_GS1_country_codes</a>.
/// </summary>
TEANManufacturerOrgSupport = class sealed
private
var
ranges: TList<TArray<Integer>>;
countryIdentifiers: TStringList;
procedure add(const range: TArray<Integer>; const id: string);
procedure initIfNeeded;
public
constructor Create;
destructor Destroy; override;
function lookupCountryIdentifier(const productCode: String): String;
end;
implementation
{ TEANManufacturerOrgSupport }
constructor TEANManufacturerOrgSupport.Create;
begin
ranges := TList < TArray < Integer >>.Create;
countryIdentifiers := TStringList.Create;
end;
destructor TEANManufacturerOrgSupport.Destroy;
begin
countryIdentifiers.Free;
ranges.Free;
inherited;
end;
function TEANManufacturerOrgSupport.lookupCountryIdentifier(const productCode
: String): String;
var
i, prefix, max: Integer;
range: TArray<Integer>;
start, ending: Integer;
begin
Result := '';
initIfNeeded;
prefix := Integer.Parse(productCode.Substring(0, 3));
max := ranges.Count;
for i := 0 to Pred(max) do
begin
range := ranges[i];
start := range[0];
if (prefix < start) then
exit;
if (Length(range) = 1) then
ending := start
else
ending := range[1];
if (prefix <= ending) then
begin
Result := countryIdentifiers[i];
break;
end;
end;
end;
procedure TEANManufacturerOrgSupport.add(const range: TArray<Integer>;
const id: string);
begin
ranges.add(range);
countryIdentifiers.add(id);
end;
procedure TEANManufacturerOrgSupport.initIfNeeded;
begin
if (ranges.Count = 0) then
begin
add(TArray<Integer>.Create(0, 19), 'US/CA');
add(TArray<Integer>.Create(30, 39), 'US');
add(TArray<Integer>.Create(60, 139), 'US/CA');
add(TArray<Integer>.Create(300, 379), 'FR');
add(TArray<Integer>.Create(380), 'BG');
add(TArray<Integer>.Create(383), 'SI');
add(TArray<Integer>.Create(385), 'HR');
add(TArray<Integer>.Create(387), 'BA');
add(TArray<Integer>.Create(400, 440), 'DE');
add(TArray<Integer>.Create(450, 459), 'JP');
add(TArray<Integer>.Create(460, 469), 'RU');
add(TArray<Integer>.Create(471), 'TW');
add(TArray<Integer>.Create(474), 'EE');
add(TArray<Integer>.Create(475), 'LV');
add(TArray<Integer>.Create(476), 'AZ');
add(TArray<Integer>.Create(477), 'LT');
add(TArray<Integer>.Create(478), 'UZ');
add(TArray<Integer>.Create(479), 'LK');
add(TArray<Integer>.Create(480), 'PH');
add(TArray<Integer>.Create(481), 'BY');
add(TArray<Integer>.Create(482), 'UA');
add(TArray<Integer>.Create(484), 'MD');
add(TArray<Integer>.Create(485), 'AM');
add(TArray<Integer>.Create(486), 'GE');
add(TArray<Integer>.Create(487), 'KZ');
add(TArray<Integer>.Create(489), 'HK');
add(TArray<Integer>.Create(490, 499), 'JP');
add(TArray<Integer>.Create(500, 509), 'GB');
add(TArray<Integer>.Create(520), 'GR');
add(TArray<Integer>.Create(528), 'LB');
add(TArray<Integer>.Create(529), 'CY');
add(TArray<Integer>.Create(531), 'MK');
add(TArray<Integer>.Create(535), 'MT');
add(TArray<Integer>.Create(539), 'IE');
add(TArray<Integer>.Create(540, 549), 'BE/LU');
add(TArray<Integer>.Create(560), 'PT');
add(TArray<Integer>.Create(569), 'IS');
add(TArray<Integer>.Create(570, 579), 'DK');
add(TArray<Integer>.Create(590), 'PL');
add(TArray<Integer>.Create(594), 'RO');
add(TArray<Integer>.Create(599), 'HU');
add(TArray<Integer>.Create(600, 601), 'ZA');
add(TArray<Integer>.Create(603), 'GH');
add(TArray<Integer>.Create(608), 'BH');
add(TArray<Integer>.Create(609), 'MU');
add(TArray<Integer>.Create(611), 'MA');
add(TArray<Integer>.Create(613), 'DZ');
add(TArray<Integer>.Create(616), 'KE');
add(TArray<Integer>.Create(618), 'CI');
add(TArray<Integer>.Create(619), 'TN');
add(TArray<Integer>.Create(621), 'SY');
add(TArray<Integer>.Create(622), 'EG');
add(TArray<Integer>.Create(624), 'LY');
add(TArray<Integer>.Create(625), 'JO');
add(TArray<Integer>.Create(626), 'IR');
add(TArray<Integer>.Create(627), 'KW');
add(TArray<Integer>.Create(628), 'SA');
add(TArray<Integer>.Create(629), 'AE');
add(TArray<Integer>.Create(640, 649), 'FI');
add(TArray<Integer>.Create(690, 695), 'CN');
add(TArray<Integer>.Create(700, 709), 'NO');
add(TArray<Integer>.Create(729), 'IL');
add(TArray<Integer>.Create(730, 739), 'SE');
add(TArray<Integer>.Create(740), 'GT');
add(TArray<Integer>.Create(741), 'SV');
add(TArray<Integer>.Create(742), 'HN');
add(TArray<Integer>.Create(743), 'NI');
add(TArray<Integer>.Create(744), 'CR');
add(TArray<Integer>.Create(745), 'PA');
add(TArray<Integer>.Create(746), 'DO');
add(TArray<Integer>.Create(750), 'MX');
add(TArray<Integer>.Create(754, 755), 'CA');
add(TArray<Integer>.Create(759), 'VE');
add(TArray<Integer>.Create(760, 769), 'CH');
add(TArray<Integer>.Create(770), 'CO');
add(TArray<Integer>.Create(773), 'UY');
add(TArray<Integer>.Create(775), 'PE');
add(TArray<Integer>.Create(777), 'BO');
add(TArray<Integer>.Create(779), 'AR');
add(TArray<Integer>.Create(780), 'CL');
add(TArray<Integer>.Create(784), 'PY');
add(TArray<Integer>.Create(785), 'PE');
add(TArray<Integer>.Create(786), 'EC');
add(TArray<Integer>.Create(789, 790), 'BR');
add(TArray<Integer>.Create(800, 839), 'IT');
add(TArray<Integer>.Create(840, 849), 'ES');
add(TArray<Integer>.Create(850), 'CU');
add(TArray<Integer>.Create(858), 'SK');
add(TArray<Integer>.Create(859), 'CZ');
add(TArray<Integer>.Create(860), 'YU');
add(TArray<Integer>.Create(865), 'MN');
add(TArray<Integer>.Create(867), 'KP');
add(TArray<Integer>.Create(868, 869), 'TR');
add(TArray<Integer>.Create(870, 879), 'NL');
add(TArray<Integer>.Create(880), 'KR');
add(TArray<Integer>.Create(885), 'TH');
add(TArray<Integer>.Create(888), 'SG');
add(TArray<Integer>.Create(890), 'IN');
add(TArray<Integer>.Create(893), 'VN');
add(TArray<Integer>.Create(896), 'PK');
add(TArray<Integer>.Create(899), 'ID');
add(TArray<Integer>.Create(900, 919), 'AT');
add(TArray<Integer>.Create(930, 939), 'AU');
add(TArray<Integer>.Create(940, 949), 'AZ');
add(TArray<Integer>.Create(955), 'MY');
add(TArray<Integer>.Create(958), 'MO');
end;
end;
end.
@@ -0,0 +1,497 @@
{
* Copyright 2008 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 Author: kevin.osullivan@sita.aero, SITA Lab.
* Delphi Implementation by E. Spelt and K. Gossens
}
unit ZXing.OneD.ITFReader;
interface
uses
System.SysUtils,
System.Generics.Collections,
Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat,
ZXing.Helpers;
type
/// <summary>
/// <p>Implements decoding of the ITF format, or Interleaved Two of Five.</p>
///
/// <p>This Reader will scan ITF barcodes of certain lengths only.
/// At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, 44 and 48 as these have appeared "in the wild". Not all
/// lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
/// required checksum function.</p>
///
/// <p>The checksum is optional and is not applied by this Reader. The consumer of the decoded
/// value will have to apply a checksum if required.</p>
///
/// <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>
/// is a great reference for Interleaved 2 of 5 information.</p>
/// </summary>
TITFReader = class(TOneDReader)
const
LARGEST_DEFAULT_ALLOWED_LENGTH: Integer = 14;
N: Integer = 1;
W: Integer = 3;
private
class var PATTERNS: TArray<TArray<Integer>>;
class var END_PATTERN_REVERSED: TArray<Integer>;
class var MAX_AVG_VARIANCE: Integer;
class var MAX_INDIVIDUAL_VARIANCE: Integer;
class var DEFAULT_ALLOWED_LENGTHS: TArray<Integer>;
class var
START_PATTERN: TArray<Integer>;
narrowLineWidth: Integer;
class function decodeDigit(counters: TArray<Integer>;
out bestMatch: Integer): boolean; static;
function decodeEnd(row: IBitArray): TArray<Integer>;
class function skipWhiteSpace(row: IBitArray): Integer; static;
class function findGuardPattern(row: IBitArray; rowOffset: Integer;
pattern: TArray<Integer>): TArray<Integer>; static;
function validateQuietZone(row: IBitArray; startPattern: Integer): boolean;
function decodeStart(row: IBitArray): TArray<Integer>;
class function decodeMiddle(row: IBitArray;
payloadStart, payloadEnd: Integer; SBResult: TStringBuilder)
: boolean; static;
class procedure ClassInit();
public
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult; override;
end;
implementation
uses
ZXing.Common.Detector.MathUtils;
{ TITFReader }
class procedure TITFReader.ClassInit();
begin
MAX_AVG_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42);
MAX_INDIVIDUAL_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.78);
DEFAULT_ALLOWED_LENGTHS := [6, 8, 10, 12, 14];
START_PATTERN := [N, N, N, N];
END_PATTERN_REVERSED := [N, N, W];
PATTERNS := [[N, N, W, W, N], // 0
[W, N, N, N, W], // 1
[N, W, N, N, W], // 2
[W, W, N, N, N], // 3
[N, N, W, N, W], // 4
[W, N, W, N, N], // 5
[N, W, W, N, N], // 6
[N, N, N, W, W], // 7
[W, N, N, W, N], // 8
[N, W, N, W, N]]; // 9
end;
function TITFReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
var
allowedLength: Integer;
startRange: TArray<Integer>;
endRange: TArray<Integer>;
stringResult: string;
allowedLengths: TArray<Integer>;
maxAllowedLength: Integer;
len: Integer;
lengthOK: boolean;
SBResult: TStringBuilder;
resultPointCallback: TResultPointCallback;
obj: TObject;
resultPoints: TArray<IResultPoint>;
resultPointLeft,
resultPointRight: IResultPoint;
begin
startRange := decodeStart(row);
if (startRange = nil) then
begin
Exit(nil);
end;
endRange := self.decodeEnd(row);
if (endRange = nil) then
begin
Exit(nil);
end;
SBResult := TStringBuilder.Create(20);
if (not decodeMiddle(row, startRange[1], endRange[0], SBResult)) then
begin
FreeAndNil(sbResult);
Exit(nil);
end;
stringResult := SBResult.ToString();
FreeAndNil(sbResult);
allowedLengths := nil;
maxAllowedLength := LARGEST_DEFAULT_ALLOWED_LENGTH;
if ((hints <> nil) and hints.ContainsKey(ZXing.DecodeHintType.ALLOWED_LENGTHS)) then
begin
allowedLengths := TArray<Integer>(hints[ZXing.DecodeHintType.ALLOWED_LENGTHS]);
maxAllowedLength := 0
end;
if (allowedLengths = nil) then
begin
allowedLengths := DEFAULT_ALLOWED_LENGTHS;
maxAllowedLength := LARGEST_DEFAULT_ALLOWED_LENGTH;
end;
len := Length(stringResult);
lengthOK := (len > LARGEST_DEFAULT_ALLOWED_LENGTH);
if (not lengthOK) then
begin
for allowedLength in allowedLengths do
begin
if (len = allowedLength) then
begin
lengthOK := true;
break;
end;
if (allowedLength > maxAllowedLength) then
begin
maxAllowedLength := allowedLength;
end;
end;
if ((not lengthOK) and (len > maxAllowedLength)) then
begin
lengthOK := true;
end;
if (not lengthOK) then
begin
Exit(nil);
end
end;
resultPointLeft := TResultPointHelpers.CreateResultPoint(startRange[1], rowNumber);
resultPointRight := TResultPointHelpers.CreateResultPoint(endRange[0], rowNumber);
resultPoints := [resultPointLeft, resultPointRight];
resultPointCallback := nil; // had to add this initialization because the following if/else construct does not assign a value
// for all possible cases. Being resultPointCallback a local variable it is not initialized by default to null,
// so you could get unpredictable results
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(stringResult, nil, resultPoints,
TBarcodeFormat.ITF);
end;
class function TITFReader.decodeDigit(counters: TArray<Integer>;
out bestMatch: Integer): boolean;
var
bestVariance: Integer;
max: Integer;
i: Integer;
pattern: TArray<Integer>;
variance: Integer;
begin
bestVariance := MAX_AVG_VARIANCE;
bestMatch := -1;
max := Length(PATTERNS);
i := 0;
while ((i < max)) do
begin
pattern := PATTERNS[i];
variance := PatternMatchVariance(counters, pattern,
MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance) then
begin
bestVariance := variance;
bestMatch := i
end;
inc(i)
end;
Result := (bestMatch >= 0);
end;
function TITFReader.decodeStart(row: IBitArray): TArray<Integer>;
var
endStart: Integer;
startPattern: TArray<Integer>;
begin
endStart := skipWhiteSpace(row);
if (endStart < 0) then
begin
Exit(nil);
end;
startPattern := findGuardPattern(row, endStart, START_PATTERN);
if (startPattern = nil) then
begin
Exit(nil);
end;
narrowLineWidth := TMathUtils.Asr(startPattern[1] - startPattern[0], 2);
if (not validateQuietZone(row, startPattern[0])) then
begin
Exit(nil);
end;
Result := startPattern;
end;
class function TITFReader.decodeMiddle(row: IBitArray;
payloadStart, payloadEnd: Integer; SBResult: TStringBuilder): boolean;
var
bestMatch: Integer;
counterDigit: Integer;
counterDigitPair: TArray<Integer>;
counterBlack: TArray<Integer>;
counterWhite: TArray<Integer>;
k: Integer;
twoK: Integer;
begin
SetLength(counterDigitPair, 10);
SetLength(counterBlack, 5);
SetLength(counterWhite, 5);
while ((payloadStart < payloadEnd)) do
begin
if (not RecordPattern(row, payloadStart, counterDigitPair)) then
begin
Exit(false);
end;
k := 0;
while ((k < 5)) do
begin
twoK := (k shl 1);
counterBlack[k] := counterDigitPair[twoK];
counterWhite[k] := counterDigitPair[(twoK + 1)];
inc(k)
end;
if (not decodeDigit(counterBlack, bestMatch)) then // @(bestMatch))) then
begin
Exit(false);
end;
SBResult.Append(char($30 + bestMatch));
if (not decodeDigit(counterWhite, bestMatch)) then // @(bestMatch))) then
begin
Exit(false);
end;
SBResult.Append(char($30 + bestMatch));
for counterDigit in counterDigitPair do
begin
inc(payloadStart, counterDigit);
end
end;
Result := true;
end;
function TITFReader.decodeEnd(row: IBitArray): TArray<Integer>;
var
endStart: Integer;
endPattern: TArray<Integer>;
temp: Integer;
begin
row.reverse;
endStart := skipWhiteSpace(row);
if (endStart < 0) then
begin
Exit(nil);
end;
// !!
endPattern := findGuardPattern(row, endStart, END_PATTERN_REVERSED);
if (endPattern = nil) then
begin
row.reverse;
Exit(nil);
end;
if (not validateQuietZone(row, endPattern[0])) then
begin
row.reverse;
Exit(nil);
end;
temp := endPattern[0];
endPattern[0] := row.Size - endPattern[1];
endPattern[1] := row.Size - temp;
row.reverse;
Result := endPattern;
end;
class function TITFReader.findGuardPattern(row: IBitArray; rowOffset: Integer;
pattern: TArray<Integer>): TArray<Integer>;
var
patternLength: Integer;
counters: TArray<Integer>;
width: Integer;
isWhite: boolean;
counterPosition: Integer;
patternStart: Integer;
x: Integer;
begin
patternLength := Length(pattern);
counters := TArray<Integer>.Create();
SetLength(counters, patternLength);
width := row.Size;
isWhite := false;
counterPosition := 0;
patternStart := rowOffset;
x := rowOffset;
while ((x < width)) do
begin
if (row[x] xor isWhite) then
begin
inc(counters[counterPosition]);
end
else
begin
if (counterPosition = (patternLength - 1)) then
begin
if (PatternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) <
MAX_AVG_VARIANCE) then
begin
Result := [patternStart, x];
Exit(Result);
end;
inc(patternStart, (counters[0] + counters[1]));
counters := TArray.CopyInSameArray(counters, 2, patternLength - 2);
counters[(patternLength - 2)] := 0;
counters[(patternLength - 1)] := 0;
dec(counterPosition);
end
else
begin
inc(counterPosition);
end;
counters[counterPosition] := 1;
isWhite := not isWhite
end;
inc(x);
end;
Result := nil;
end;
class function TITFReader.skipWhiteSpace(row: IBitArray): Integer;
var
width: Integer;
endStart: Integer;
begin
width := row.Size;
endStart := row.getNextSet(0);
if (endStart = width) then
begin
Result := -1;
end
else
begin
Result := endStart;
end
end;
function TITFReader.validateQuietZone(row: IBitArray;
startPattern: Integer): boolean;
var
quietCount: Integer;
i: Integer;
begin
quietCount := (self.narrowLineWidth * 10);
if (quietCount >= startPattern) then
begin
quietCount := startPattern;
end;
i := (startPattern - 1);
while (((quietCount > 0) and (i >= 0))) do
begin
if (row[i]) then
begin
break;
end;
dec(quietCount);
dec(i)
end;
if (quietCount <> 0) then
begin
Exit(false);
end;
Result := true;
end;
initialization
TITFReader.ClassInit();
end.
@@ -0,0 +1,499 @@
{
* Copyright 2007 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: dswitkin@google.com (Daniel Switkin) and Sean Owen
* Delphi Implementation by E. Spelt and K. Gossens
}
unit ZXing.OneD.OneDReader;
interface
uses
System.SysUtils,
System.Generics.Collections,
Math,
ZXing.Reader,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultMetadataType,
ZXing.ResultPoint,
ZXing.Common.BitArray,
ZXing.Common.Detector.MathUtils;
type
/// <summary>
/// Encapsulates functionality and implementation that is common to all families
/// of one-dimensional barcodes.
/// </summary>
TOneDReader = class(TInterfacedObject, IReader)
private
/// <summary>
/// We're going to examine rows from the middle outward, searching alternately above and below the
/// middle, and farther out each time. rowStep is the number of rows between each successive
/// attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
/// middle + rowStep, then middle - (2 * rowStep), etc.
/// rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
/// decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
/// image if "trying harder".
/// </summary>
/// <param name="image">The image to decode</param>
/// <param name="hints">Any hints that were requested</param>
/// <returns>The contents of the decoded barcode</returns>
function doDecode(const image: TBinaryBitmap;
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
class procedure InitializeClass; static;
protected
class var INTEGER_MATH_SHIFT: Integer;
class var PATTERN_MATCH_RESULT_SCALE_FACTOR: Integer;
/// <summary>
/// Determines how closely a set of observed counts of runs of black/white values matches a given
/// target pattern. This is reported as the ratio of the total variance from the expected pattern
/// proportions across all pattern elements, to the length of the pattern.
/// </summary>
/// <param name="counters">observed counters</param>
/// <param name="pattern">expected pattern</param>
/// <param name="maxIndividualVariance">The most any counter can differ before we give up</param>
/// <returns>ratio of total variance between counters and pattern compared to total pattern size,
/// where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
/// the total variance between counters and patterns equals the pattern length, higher values mean
/// even more variance</returns>
class function patternMatchVariance(counters, pattern: TArray<Integer>;
maxIndividualVariance: Integer): Integer; static;
/// <summary>
/// Records the pattern in reverse.
/// </summary>
/// <param name="row">The row.</param>
/// <param name="start">The start.</param>
/// <param name="counters">The counters.</param>
/// <returns></returns>
class function RecordPatternInReverse(row: IBitArray; start: Integer;
counters: TArray<Integer>): Boolean; Static;
/// <summary>
/// Records the size of successive runs of white and black pixels in a row, starting at a given point.
/// The values are recorded in the given array, and the number of runs recorded is equal to the size
/// of the array. If the row starts on a white pixel at the given start point, then the first count
/// recorded is the run of white pixels starting from that point; likewise it is the count of a run
/// of black pixels if the row begin on a black pixels at that point.
/// </summary>
/// <param name="row">row to count from</param>
/// <param name="start">offset into row to start at</param>
/// <param name="counters">array into which to record counts</param>
class function recordPattern(row: IBitArray; start: Integer;
counters: TArray<Integer>; numCounters: Integer): Boolean;
overload; static;
public
/// <summary>
/// Resets any internal state the implementation has after a decode, to prepare it
/// for reuse.
/// </summary>
procedure reset(); virtual;
/// <summary>
/// Locates and decodes a barcode in some format within an image.
/// </summary>
/// <param name="image">image of barcode to decode</param>
/// <returns>
/// String which the barcode encodes
/// </returns>
function decode(const image: TBinaryBitmap): TReadResult; overload;
/// <summary>
/// Locates and decodes a barcode in some format within an image. This method also accepts
/// hints, each possibly associated to some data, which may help the implementation decode.
/// Note that we don't try rotation without the try harder flag, even if rotation was supported.
/// </summary>
/// <param name="image">image of barcode to decode</param>
/// <param name="hints">passed as a <see cref="IDictionary{TKey, TValue}"/> from <see cref="DecodeHintType"/>
/// to arbitrary data. The
/// meaning of the data depends upon the hint type. The implementation may or may not do
/// anything with these hints.</param>
/// <returns>
/// String which the barcode encodes
/// </returns>
function decode(const image: TBinaryBitmap;
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
overload; virtual;
/// <summary>
/// Records the size of successive runs of white and black pixels in a row, starting at a given point.
/// The values are recorded in the given array, and the number of runs recorded is equal to the size
/// of the array. If the row starts on a white pixel at the given start point, then the first count
/// recorded is the run of white pixels starting from that point; likewise it is the count of a run
/// of black pixels if the row begin on a black pixels at that point.
/// </summary>
/// <param name="row">row to count from</param>
/// <param name="start">offset into row to start at</param>
/// <param name="counters">array into which to record counts</param>
class function recordPattern(row: IBitArray; start: Integer;
counters: TArray<Integer>): Boolean; overload; static;
/// <summary>
/// Attempts to decode a one-dimensional barcode format given a single row of
/// an image.
/// </summary>
/// <param name="rowNumber">row number from top of the row</param>
/// <param name="row">the black/white pixel data of the row</param>
/// <param name="hints">decode hints</param>
/// <returns>
/// <see cref="Result"/>containing encoded string and start/end of barcode
/// </returns>
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
virtual; abstract;
end;
implementation
{ TOneDReader }
function TOneDReader.decode(const image: TBinaryBitmap): TReadResult;
begin
Result := decode(image, nil);
end;
class procedure TOneDReader.InitializeClass();
begin
INTEGER_MATH_SHIFT := 8;
PATTERN_MATCH_RESULT_SCALE_FACTOR := (1 shl INTEGER_MATH_SHIFT);
end;
function TOneDReader.decode(const image: TBinaryBitmap;
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
var
tryHarder, tryHarderWithoutRotation: Boolean;
rotatedImage: TBinaryBitmap;
metadata: TResultMetadata;
orientation, height, i, l: Integer;
points: TArray<IResultPoint>;
begin
Result := doDecode(image, hints);
if (Result = nil) then
begin
Exit;
end;
tryHarder := (hints <> nil) and
(hints.ContainsKey(ZXing.DecodeHintType.TRY_HARDER));
tryHarderWithoutRotation := (hints <> nil) and
(hints.ContainsKey(ZXing.DecodeHintType.TRY_HARDER_WITHOUT_ROTATION));
if (tryHarder and ((not tryHarderWithoutRotation) and image.RotateSupported))
then
begin
rotatedImage := image.rotateCounterClockwise();
Result := doDecode(rotatedImage, hints);
if (Result = nil) then
begin
Exit;
end;
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
metadata := Result.ResultMetadata;
orientation := 270;
if ((metadata <> nil) and metadata.ContainsKey
(ZXing.ResultMetadataType.orientation)) then
begin
// But if we found it reversed in doDecode(), add in that result here:
orientation :=
(orientation + (metadata[ZXing.ResultMetadataType.orientation]
as IIntegerMetadata).Value) mod 360;
end;
Result.putMetadata(ZXing.ResultMetadataType.orientation,
TResultMetadata.CreateIntegerMetadata(orientation));
// Update result points
points := Result.ResultPoints;
if (points <> nil) then
begin
height := rotatedImage.height;
l := Length(points) - 1;
for i := 0 to l do
begin
points[i] := TResultPointHelpers.CreateResultPoint
(height - points[i].Y - 1, points[i].X);
end;
end;
end;
end;
function TOneDReader.doDecode(const image: TBinaryBitmap;
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
var
attempt, X, rowNumber, rowStepsAboveOrBelow, width, height, middle, rowStep,
maxLines: Integer;
row: IBitArray;
isAbove, hadResultPointCallBack: Boolean;
ReadResult: TReadResult;
points: TArray<IResultPoint>;
obj: TObject;
needsCallBack : boolean;
begin
needsCallBack := (hints <> nil) and (hints.ContainsKey(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK));
width := image.width;
height := image.height;
row := TBitArrayHelpers.CreateBitArray(width);
// row is a interfaced object: we don't need to free it explicitly
middle := TMathUtils.Asr(height, 1);
rowStep := 5;
maxLines := height; // Look at the whole image, not just the center
for X := 0 to maxLines - 1 do
begin
// Scanning from the middle out. Determine which row we're looking at next:
rowStepsAboveOrBelow := TMathUtils.Asr((X + 1), 1);
isAbove := (X and $01) = 0; // i.e. is x even?
if (not isAbove) then
begin
rowStepsAboveOrBelow := rowStepsAboveOrBelow * -1;
end;
rowNumber := middle + rowStep * rowStepsAboveOrBelow;
if ((rowNumber < 0) or (rowNumber >= height)) then
begin
// Oops, if we run off the top or bottom, stop
break;
end;
// Estimate black point for this row and load it:
row := image.getBlackRow(rowNumber, row);
if (row = nil) then
begin
continue;
end;
// While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
// handle decoding upside down barcodes.
// for attempt := 0 to (attempt < 2) do
for attempt := 0 to 1 do
begin
hadResultPointCallBack := false;
if (attempt = 1) then
begin
// trying again?
row.Reverse();
// This means we will only ever draw result points *once* in the life of this method
// since we want to avoid drawing the wrong points after flipping the row, and,
// don't want to clutter with noise from every single row scan -- just the scans
// that start on the center line.
if needsCallBack then
begin
hints.TryGetValue
(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK, obj);
hints.Remove(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK);
hadResultPointCallBack := true;
end;
end;
// Look for a barcode
ReadResult := decodeRow(rowNumber, row, hints);
if hadResultPointCallBack then
begin
hints.Add(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK, obj);
end;
if (ReadResult = nil) then
begin
continue;
end;
// We found our barcode
if (attempt = 1) then
begin
// But it was upside down, so note that
// ReadResult.putMetadata(ResultMetadataType.orientation, TObject(180));
// And remember to flip the result points horizontally.
points := ReadResult.ResultPoints;
if (points <> nil) then
begin
points[0] := TResultPointHelpers.CreateResultPoint
(width - points[0].X - 1, points[0].Y);
points[1] := TResultPointHelpers.CreateResultPoint
(width - points[1].X - 1, points[1].Y);
end;
end;
Exit(ReadResult);
end; // loop
end;
Result := nil;
end;
class function TOneDReader.patternMatchVariance(counters,
pattern: TArray<Integer>; maxIndividualVariance: Integer): Integer;
var
scaledPattern, variance, counter, totalVariance, X, unitBarWidth, i,
patternLength, numCounters, total: Integer;
begin
Result := High(Integer);
numCounters := Length(counters);
total := 0;
patternLength := 0;
for i := 0 to numCounters - 1 do
begin
total := total + counters[i];
patternLength := patternLength + pattern[i];
end;
if (total < patternLength) then
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
Exit;
// We're going to fake floating-point math in integers. We just need to use more bits.
// Scale up patternLength so that intermediate values below like scaledCounter will have
// more "significant digits"
unitBarWidth := (total shl INTEGER_MATH_SHIFT) div patternLength;
maxIndividualVariance :=
TMathUtils.Asr((maxIndividualVariance * unitBarWidth), INTEGER_MATH_SHIFT);
totalVariance := 0;
for X := 0 to numCounters - 1 do
begin
counter := counters[X] shl INTEGER_MATH_SHIFT;
scaledPattern := pattern[X] * unitBarWidth;
if (counter > scaledPattern) then
variance := counter - scaledPattern
else
variance := scaledPattern - counter;
if (variance > maxIndividualVariance) then
Exit;
totalVariance := totalVariance + variance;
end;
Result := totalVariance div total;
end;
class function TOneDReader.recordPattern(row: IBitArray; start: Integer;
counters: TArray<Integer>): Boolean;
begin
Result := recordPattern(row, start, counters, Length(counters));
end;
class function TOneDReader.recordPattern(row: IBitArray; start: Integer;
counters: TArray<Integer>; numCounters: Integer): Boolean;
var
i, counterPosition, idx, ending: Integer;
isWhite: Boolean;
begin
for idx := 0 to numCounters - 1 do
begin
counters[idx] := 0;
end;
ending := row.Size;
if (start >= ending) then
begin
Result := false;
Exit;
end;
isWhite := not row[start];
counterPosition := 0;
i := start;
while (i < ending) do
begin
if (row[i] xor isWhite) then
begin // that is, exactly one is true
inc(counters[counterPosition]);
end
else
begin
inc(counterPosition);
if (counterPosition = numCounters) then
begin
break;
end
else
begin
counters[counterPosition] := 1;
isWhite := not isWhite;
end;
end;
inc(i);
end;
// If we read fully the last section of pixels and filled up our counters -- or filled
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
Result := ((counterPosition = numCounters) or
((counterPosition = (numCounters - 1)) and (i = ending)));
end;
class function TOneDReader.RecordPatternInReverse(row: IBitArray;
start: Integer; counters: TArray<Integer>): Boolean;
var
numTransitionsLeft: Integer;
last: Boolean;
begin
// This could be more efficient I guess
numTransitionsLeft := Length(counters);
last := row[start];
while ((start > 0) and (numTransitionsLeft >= 0)) do
begin
dec(start);
if (row[start] <> last) then
begin
dec(numTransitionsLeft);
last := not last;
end;
end;
if (numTransitionsLeft >= 0) then
begin
Result := false;
Exit;
end;
Result := recordPattern(row, start + 1, counters);
end;
procedure TOneDReader.reset;
begin
// do nothing
end;
initialization
TOneDReader.InitializeClass();
end.
@@ -0,0 +1,140 @@
unit ZXing.OneD.UPCAReader;
{
* Copyright 2008 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
}
interface
uses
System.SysUtils,
System.Generics.Collections,
Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat,
ZXing.Helpers,
ZXing.OneD.EAN13Reader,
ZXing.OneD.UPCEANReader;
type
TUPCAReader = class(TUPCEANReader)
private
class var EAN13Reader: TUPCEANReader;
class function maybeReturnResult(pResult: TReadResult): TReadResult; static;
class procedure DoInitialize();
class procedure DoFinalize();
protected
public
class function DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const resultString: TStringBuilder)
: Integer; override;
function decode(const image: TBinaryBitmap;
hints: TDictionary<TDecodeHintType, TObject>): TReadResult; override;
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
overload; override;
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const startGuardRange: TArray<Integer>;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
reintroduce; overload;
function BarcodeFormat: TBarcodeFormat; override;
end;
implementation
{ TUPCAReader }
class procedure TUPCAReader.DoFinalize;
begin
EAN13Reader.Free;
end;
class procedure TUPCAReader.DoInitialize;
begin
EAN13Reader := TEAN13Reader.Create();
end;
function TUPCAReader.decode(const image: TBinaryBitmap;
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
begin
result := TUPCAReader.maybeReturnResult(self.EAN13Reader.decode(image, hints))
end;
class function TUPCAReader.DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>;
const resultString: TStringBuilder): Integer;
begin
result := self.EAN13Reader.DecodeMiddle(row, startRange, resultString)
end;
function TUPCAReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
begin
result := TUPCAReader.maybeReturnResult(self.EAN13Reader.decodeRow(rowNumber,
row, hints))
end;
function TUPCAReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
const startGuardRange: TArray<Integer>;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
begin
result := TUPCAReader.maybeReturnResult
(self.EAN13Reader.doDecodeRow(rowNumber, row, startGuardRange, hints))
end;
function TUPCAReader.BarcodeFormat: TBarcodeFormat;
begin
result := TBarcodeFormat.UPC_A;
end;
class function TUPCAReader.maybeReturnResult(pResult: TReadResult): TReadResult;
begin
if not Assigned(pResult) then
Exit(nil);
{$ZEROBASEDSTRINGS ON}
if (copy(pResult.text, 0, 1) = '0') then
begin
pResult.text := copy(pResult.text, 2, length(pResult.text));
pResult.BarcodeFormat := TBarcodeFormat.UPC_A;
end;
{$ZEROBASEDSTRINGS OFF}
result := pResult;
end;
initialization
TUPCAReader.DoInitialize;
finalization
TUPCAReader.DoFinalize;
end.
@@ -0,0 +1,194 @@
{
* Copyright 2008 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.
* Delphi Implementation by K. Gossens
}
unit ZXing.OneD.UPCEANExtension2Support;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.Reader,
ZXing.BarcodeFormat,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultMetadataType,
ZXing.ResultPoint,
ZXing.Common.BitArray;
type
/// <summary>
/// @see UPCEANExtension5Support
/// </summary>
TUPCEANExtension2Support = class sealed
private
class var
decodeMiddleCounters : TArray<Integer>;
decodeRowStringBuffer : TStringBuilder;
class procedure InitializeClass; static;
class procedure FinalizeClass; static;
class function decodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>;
const resultString: TStringBuilder): Integer;
/// <summary>
/// Parses the extension string.
/// </summary>
/// <param name="raw">raw content of extension</param>
/// <returns>formatted interpretation of raw content as a {@link TMap} mapping
class function parseExtensionString(const raw: String): TResultMetadata; static;
public
class function decodeRow(const rowNumber: Integer; const row: IBitArray;
const extensionStartRange: TArray<Integer>): TReadResult;
end;
implementation
uses
ZXing.OneD.UPCEANReader;
{ TUPCEANExtension2Support }
class procedure TUPCEANExtension2Support.InitializeClass;
begin
decodeMiddleCounters := TArray<Integer>.Create(0, 0, 0, 0);
decodeRowStringBuffer := TStringBuilder.Create;
end;
class procedure TUPCEANExtension2Support.FinalizeClass;
begin
decodeMiddleCounters := nil;
decodeRowStringBuffer.Free;
end;
class function TUPCEANExtension2Support.decodeRow(const rowNumber: Integer;
const row: IBitArray; const extensionStartRange: TArray<Integer>): TReadResult;
var
res : TStringBuilder;
ending : Integer;
resultString : String;
extensionResult : TReadResult;
extensionData : TResultMetadata;
resultPoints : TArray<IResultPoint>;
begin
Result := nil;
res := decodeRowStringBuffer;
res.Length := 0;
ending := decodeMiddle(row, extensionStartRange, res);
if (ending < 0)
then
exit;
resultString := res.ToString;
extensionData := TUPCEANExtension2Support.parseExtensionString(resultString);
resultPoints := TArray<IResultPoint>.Create(
TResultPointHelpers.CreateResultPoint((extensionStartRange[0] + extensionStartRange[1]) div 2, rowNumber),
TResultPointHelpers.CreateResultPoint(ending, rowNumber));
extensionResult := TReadResult.Create(resultString, nil, resultPoints, TBarcodeFormat.UPC_EAN_EXTENSION);
if (extensionData <> nil)
then
begin
extensionResult.putAllMetadata(extensionData);
FreeAndNil(extensionData);
end;
Result := extensionResult;
end;
class function TUPCEANExtension2Support.decodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const resultString: TStringBuilder): Integer;
var
bestMatch: Integer;
counter: Integer;
ending: Integer;
counters : TArray<Integer>;
rowOffset, x,
checkParity: Integer;
begin
Result := -1;
counters := decodeMiddleCounters;
counters[0] := 0;
counters[1] := 0;
counters[2] := 0;
counters[3] := 0;
ending := row.Size;
rowOffset := startRange[1];
checkParity := 0;
x := 0;
while ((x < 2) and (rowOffset < ending)) do
begin
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset, TUPCEANReader.L_AND_G_PATTERNS, bestMatch))
then
exit;
resultString.Append(IntToStr(bestMatch mod 10));
for counter in counters do
Inc(rowOffset, counter);
if (bestMatch >= 10)
then
checkParity := checkParity or (1 shl (1 - x));
if (x <> 1) then
begin
// Read off separator if not last
rowOffset := row.getNextSet(rowOffset);
rowOffset := row.getNextUnset(rowOffset);
end;
Inc(x);
end;
if (resultString.Length <> 2)
then
exit;
if ((Integer.Parse(resultString.ToString) mod 4) <> checkParity)
then
exit;
Result := rowOffset;
end;
class function TUPCEANExtension2Support.parseExtensionString(const raw: String): TResultMetadata;
var
dictionary1: TResultMetadata;
begin
Result := nil;
if (Length(raw) <> 2)
then
exit;
dictionary1 := TResultMetadata.Create;
dictionary1.Add(ZXing.ResultMetadataType.ISSUE_NUMBER, TResultMetaData.CreateStringMetadata(raw));
Result := dictionary1;
end;
initialization
TUPCEANExtension2Support.InitializeClass;
finalization
TUPCEANExtension2Support.FinalizeClass;
end.
@@ -0,0 +1,311 @@
{
* Copyright 2008 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.
* Delphi Implementation by K. Gossens
}
unit ZXing.OneD.UPCEANExtension5Support;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.Reader,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.BarcodeFormat,
ZXing.DecodeHintType,
ZXing.ResultMetadataType,
ZXing.ResultPoint,
ZXing.Common.BitArray,
ZXing.Common.Detector.MathUtils;
type
(**
* @see TUPCEANExtension2Support
*)
TUPCEANExtension5Support = class sealed
private
class var
CHECK_DIGIT_ENCODINGS : TArray<Integer>;
decodeMiddleCounters : TArray<Integer>;
decodeRowStringBuffer : TStringBuilder;
class procedure InitializeClass; static;
class procedure FinalizeClass; static;
class function decodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>;
const resultString: TStringBuilder): Integer;
class function extensionChecksum(const s: string): Integer; static;
class function determineCheckDigit(const lgPatternFound: Integer;
var checkDigit: Integer): Boolean; static;
/// <summary>
/// Parses the extension string.
/// </summary>
/// <param name="raw">raw content of extension</param>
/// <returns>formatted interpretation of raw content as a {@link TMap} mapping
/// one {@link TResultMetadataType} to appropriate value, or {@code nil} if not known</returns>
class function parseExtensionString(
const raw: string): TResultMetadata; static;
class function parseExtension5String(const raw: String): String; static;
public
class function decodeRow(const rowNumber: Integer; const row: IBitArray;
const extensionStartRange: TArray<Integer>): TReadResult;
end;
implementation
uses
ZXing.OneD.UPCEANReader;
{ TUPCEANExtension2Support }
class procedure TUPCEANExtension5Support.InitializeClass;
begin
CHECK_DIGIT_ENCODINGS := TArray<Integer>.Create($18, $14, $12, $11, $0C,
$06, $03, $0A, $09, $05);
decodeMiddleCounters := TArray<Integer>.Create(0, 0, 0, 0);
decodeRowStringBuffer := TStringBuilder.Create;
end;
class procedure TUPCEANExtension5Support.FinalizeClass;
begin
CHECK_DIGIT_ENCODINGS := nil;
decodeMiddleCounters := nil;
decodeRowStringBuffer.Free;
end;
class function TUPCEANExtension5Support.decodeRow(const rowNumber: Integer;
const row: IBitArray; const extensionStartRange: TArray<Integer>): TReadResult;
var
res : TStringBuilder;
ending : Integer;
resultString : String;
extensionData : TResultMetadata;
resultPoints : TArray<IResultPoint>;
extensionResult : TReadResult;
begin
Result := nil;
res := decodeRowStringBuffer;
res.Length := 0;
ending := decodeMiddle(row, extensionStartRange, res);
if (ending < 0)
then
exit;
resultString := res.ToString;
extensionData := parseExtensionString(resultString);
resultPoints := TArray<IResultPoint>.Create(
TResultPointHelpers.CreateResultPoint((extensionStartRange[0] + extensionStartRange[1]) div 2, rowNumber),
TResultPointHelpers.CreateResultPoint(ending, rowNumber));
extensionResult := TReadResult.Create(resultString, nil, resultPoints, TBarcodeFormat.UPC_EAN_EXTENSION);
if (extensionData <> nil)
then
begin
extensionResult.putAllMetadata(extensionData);
FreeAndNil(extensionData);
end;
Result := extensionResult;
end;
class function TUPCEANExtension5Support.decodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const resultString: TStringBuilder): Integer;
var
bestMatch: Integer;
counter: Integer;
ending: Integer;
counters : TArray<Integer>;
rowOffset, x,
lgPatternFound,
checkDigit : Integer;
begin
Result := -1;
counters := decodeMiddleCounters;
counters[0] := 0;
counters[1] := 0;
counters[2] := 0;
counters[3] := 0;
ending := row.Size;
rowOffset := startRange[1];
lgPatternFound := 0;
x := 0;
while (((x < 5) and (rowOffset < ending))) do
begin
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset, TUPCEANReader.L_AND_G_PATTERNS, bestMatch))
then
exit;
resultString.Append(IntToStr(bestMatch mod 10));
for counter in counters do
Inc(rowOffset, counter);
if (bestMatch >= 10)
then
lgPatternFound := lgPatternFound or (1 shl (4 - x));
if (x <> 4) then
begin
// Read off separator if not last
rowOffset := row.getNextSet(rowOffset);
rowOffset := row.getNextUnset(rowOffset)
end;
Inc(x);
end;
if (resultString.Length <> 5)
then
exit;
if (not determineCheckDigit(lgPatternFound, checkDigit))
then
exit;
if (extensionChecksum(resultString.ToString) <> checkDigit)
then
exit;
Result := rowOffset;
end;
class function TUPCEANExtension5Support.extensionChecksum(
const s: String): Integer;
var
len,
sum, i : Integer;
begin
len := Length(s);
sum := 0;
i := (len - 2);
while ((i >= 0)) do
begin
Inc(sum, StrToIntDef(s.Chars[i], 0));
Dec(i, 2);
end;
sum := (sum * 3);
i := (len - 1);
while ((i >= 0)) do
begin
Inc(sum, StrToIntDef(s.Chars[i], 0));
Dec(i, 2);
end;
sum := (sum * 3);
Result := (sum mod 10);
end;
class function TUPCEANExtension5Support.determineCheckDigit(
const lgPatternFound: Integer; var checkDigit: Integer): Boolean;
var
i: Integer;
begin
Result := false;
for i := 0 to Pred(10) do
begin
if (lgPatternFound = CHECK_DIGIT_ENCODINGS[i]) then
begin
Result := true;
checkDigit := i;
break;
end;
end;
end;
class function TUPCEANExtension5Support.parseExtensionString(
const raw: String): TResultMetadata;
var
value: String;
dictionary1: TResultMetadata;
begin
Result := nil;
if (Length(raw) <> 5)
then
exit;
value := TUPCEANExtension5Support.parseExtension5String(raw);
//
// if (Length(value) = 0)
// then
// Result := nil;
dictionary1 := TResultMetadata.Create();
dictionary1.Add(ZXing.ResultMetadataType.SUGGESTED_PRICE, TResultMetadata.CreateStringMetadata(value));
Result := dictionary1;
end;
class function TUPCEANExtension5Support.parseExtension5String(
const raw: String): String;
var
currency,
unitsString,
hundredthsString : string;
rawAmount,
hundredths : Integer;
begin
Result := '';
case raw[1] of
'0' : currency := '£';
'5' : currency := '$';
'9' : begin
// Reference: http://www.jollytech.com
if ('90000'.Equals(raw))
then
// No suggested retail price
exit;
if ('99991'.Equals(raw))
then
// Complementary
Result := '0.00';
if ('99990'.Equals(raw))
then
Result := 'Used';
// Otherwise... unknown currency?
currency := '';
end;
else currency := '';
end;
rawAmount := Integer.Parse(raw.Substring(1));
unitsString := ((rawAmount div 100)).ToString;
hundredths := (rawAmount mod 100);
if (hundredths < 10)
then
hundredthsString := '0' + hundredths.ToString
else
hundredthsString := hundredths.ToString;
Result := currency + unitsString + '.' + hundredthsString;
end;
initialization
TUPCEANExtension5Support.InitializeClass;
finalization
TUPCEANExtension5Support.FinalizeClass;
end.
@@ -0,0 +1,99 @@
{
* Copyright 2008 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.
* Delphi Implementation by K. Gossens
}
unit ZXing.OneD.UPCEANExtensionSupport;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.OneD.UPCEANExtension2Support,
ZXing.OneD.UPCEANExtension5Support,
ZXing.Reader,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultMetadataType,
ZXing.ResultPoint,
ZXing.Common.BitArray,
ZXing.Common.Detector.MathUtils;
type
TUPCEANExtensionSupport = class sealed
private
class var
EXTENSION_START_PATTERN : TArray<Integer>;
twoSupport : TUPCEANExtension2Support;
fiveSupport : TUPCEANExtension5Support;
class procedure InitializeClass; static;
class procedure FinalizeClass; static;
public
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const rowOffset: Integer): TReadResult;
end;
implementation
uses
ZXing.OneD.UPCEANReader;
{ TUPCEANExtensionSupport }
class procedure TUPCEANExtensionSupport.InitializeClass();
begin
EXTENSION_START_PATTERN := TArray<Integer>.Create(1, 1, 2);
twoSupport := TUPCEANExtension2Support.Create();
fiveSupport := TUPCEANExtension5Support.Create();
end;
class procedure TUPCEANExtensionSupport.FinalizeClass();
begin
EXTENSION_START_PATTERN := nil;
twoSupport.Free;
fiveSupport.Free;
end;
function TUPCEANExtensionSupport.decodeRow(const rowNumber: Integer;
const row: IBitArray; const rowOffset: Integer): TReadResult;
var
extensionStartRange: TArray<Integer>;
res : TReadResult;
begin
Result := nil;
extensionStartRange := TUPCEANReader.findGuardPattern(row, rowOffset,
false, EXTENSION_START_PATTERN);
if (extensionStartRange = nil)
then
exit;
res := fiveSupport.decodeRow(rowNumber, row, extensionStartRange);
if (res = nil)
then
res := twoSupport.decodeRow(rowNumber, row, extensionStartRange);
Result := res;
end;
initialization
TUPCEANExtensionSupport.InitializeClass;
finalization
TUPCEANExtensionSupport.FinalizeClass;
end.
@@ -0,0 +1,592 @@
{
* Copyright 2008 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: dswitkin@google.com (Daniel Switkin), Sean Owen and
* alasdair@google.com (Alasdair Mackintosh)
* Delphi Implementation by K. Gossens
}
unit ZXing.OneD.UPCEANReader;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.OneD.UPCEANExtensionSupport,
ZXing.OneD.EANManufacturerOrgSupport,
ZXing.ResultMetadataType,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat;
type
/// <summary>
/// <p>Encapsulates functionality and implementation that is common to UPC and EAN families
/// of one-dimensional barcodes.</p>
/// </summary>
TUPCEANReader = class abstract(TOneDReader)
private
decodeRowStringBuffer: TStringBuilder;
extensionReader: TUPCEANExtensionSupport;
eanManSupport: TEANManufacturerOrgSupport;
/// <summary>
/// </summary>
/// <param name="row">row of black/white values to search</param>
/// <param name="rowOffset">position to start search</param>
/// <param name="whiteFirst">if true, indicates that the pattern specifies white/black/white/...</param>
/// pixel counts, otherwise, it is interpreted as black/white/black/...
/// <param name="pattern">pattern of counts of number of black and white pixels that are being</param>
/// searched for as a pattern
/// <param name="counters">array of counters, as long as pattern, to re-use</param>
/// <returns>start/end horizontal offset of guard pattern, as an array of two ints</returns>
class function findGuardPattern(const row: IBitArray; rowOffset: Integer;
const whiteFirst: Boolean; const pattern: TArray<Integer>;
counters: TArray<Integer>): TArray<Integer>; overload;
/// <summary>
/// Computes the UPC/EAN checksum on a string of digits, and reports
/// whether the checksum is correct or not.
/// </summary>
/// <param name="s">string of digits to check</param>
/// <returns>true iff string of digits passes the UPC/EAN checksum algorithm</returns>
class function checkStandardUPCEANChecksum(const s: String)
: Boolean; static;
class procedure InitializeClass; static;
class procedure FinalizeClass; static;
protected
class var
// These two values are critical for determining how permissive the decoding will be.
// We've arrived at these values through a lot of trial and error. Setting them any higher
// lets false positives creep in quickly.
MAX_AVG_VARIANCE: Integer;
MAX_INDIVIDUAL_VARIANCE: Integer;
/// <summary>
/// Start/end guard pattern.
/// </summary>
START_END_PATTERN: TArray<Integer>;
/// <summary>
/// Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
/// </summary>
MIDDLE_PATTERN: TArray<Integer>;
/// <summary>
/// "Odd", or "L" patterns used to encode UPC/EAN digits.
/// </summary>
L_PATTERNS: TArray<TArray<Integer>>;
/// <summary>
/// Decodes the end.
/// </summary>
/// <param name="row">The row.</param>
/// <param name="endStart">The end start.</param>
/// <returns></returns>
class function decodeEnd(const row: IBitArray; const endStart: Integer)
: TArray<Integer>; virtual;
/// <summary>
/// </summary>
/// <param name="s">string of digits to check</param>
/// <returns>see <see cref="checkStandardUPCEANChecksum(String)"/></returns>
class function checkChecksum(const s: String): Boolean; virtual;
public
class var
/// <summary>
/// As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
/// </summary>
L_AND_G_PATTERNS: TArray<TArray<Integer>>;
/// <summary>
/// Initializes a new instance of the <see cref="TUPCEANReader"/> class.
/// </summary>
constructor Create; virtual;
destructor Destroy; override;
/// <summary>
/// Subclasses override this to decode the portion of a barcode between the start
/// and end guard patterns.
/// </summary>
/// <param name="row">row of black/white values to search</param>
/// <param name="startRange">start/end offset of start guard pattern</param>
/// <param name="resultString"><see cref="StringBuilder" />to append decoded chars to</param>
/// <returns>horizontal offset of first pixel after the "middle" that was decoded or -1 if decoding could not complete successfully</returns>
class function DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const resultString: TStringBuilder)
: Integer; virtual; abstract;
/// <summary>
/// Get the format of this decoder.
/// </summary>
/// <returns>The 1D format.</returns>
function BarcodeFormat: TBarcodeFormat; virtual; abstract;
class function findStartGuardPattern(const row: IBitArray)
: TArray<Integer>; static;
class function findGuardPattern(const row: IBitArray;
const rowOffset: Integer; const whiteFirst: Boolean;
const pattern: TArray<Integer>): TArray<Integer>; overload;
/// <summary>
/// Attempts to decode a single UPC/EAN-encoded digit.
/// </summary>
/// <param name="row">row of black/white values to decode</param>
/// <param name="counters">the counts of runs of observed black/white/black/... values</param>
/// <param name="rowOffset">horizontal offset to start decoding from</param>
/// <param name="patterns">the set of patterns to use to decode -- sometimes different encodings</param>
/// for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
/// be used
/// <returns>horizontal offset of first pixel beyond the decoded digit</returns>
class function decodeDigit(const row: IBitArray;
const counters: TArray<Integer>; const rowOffset: Integer;
const patterns: TArray<TArray<Integer>>; var digit: Integer): Boolean;
/// <summary>
/// <p>Attempts to decode a one-dimensional barcode format given a single row of
/// an image.</p>
/// </summary>
/// <param name="rowNumber">row number from top of the row</param>
/// <param name="row">the black/white pixel data of the row</param>
/// <param name="hints">decode hints</param>
/// <returns>
/// <see cref="TReadResult"/>containing encoded string and start/end of barcode or null, if an error occurs or barcode cannot be found
/// </returns>
function decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
overload; override;
/// <summary>
/// <p>Like decodeRow(int, BitArray, java.util.Map), but
/// allows caller to inform method about where the UPC/EAN start pattern is
/// found. This allows this to be computed once and reused across many implementations.</p>
/// </summary>
/// <param name="rowNumber">row index into the image</param>
/// <param name="row">encoding of the row of the barcode image</param>
/// <param name="startGuardRange">start/end column where the opening start pattern was found</param>
/// <param name="hints">optional hints that influence decoding</param>
/// <returns><see cref="TReadResult"/> encapsulating the result of decoding a barcode in the row</returns>
function DoDecodeRow(const rowNumber: Integer; const row: IBitArray;
const startGuardRange: TArray<Integer>;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
end;
implementation
{ TUPCEANReader }
constructor TUPCEANReader.Create;
begin
inherited;
decodeRowStringBuffer := TStringBuilder.Create(20);
extensionReader := TUPCEANExtensionSupport.Create;
eanManSupport := TEANManufacturerOrgSupport.Create;
end;
destructor TUPCEANReader.Destroy;
begin
eanManSupport.Free;
extensionReader.Free;
decodeRowStringBuffer.Free;
inherited;
end;
class procedure TUPCEANReader.InitializeClass;
var
i, j: Integer;
widths, reversedWidths: TArray<Integer>;
begin
MAX_AVG_VARIANCE :=
Trunc(TOneDReader.PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.48);
MAX_INDIVIDUAL_VARIANCE :=
Trunc(TOneDReader.PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7);
START_END_PATTERN := TArray<Integer>.Create(1, 1, 1);
MIDDLE_PATTERN := TArray<Integer>.Create(1, 1, 1, 1, 1);
L_PATTERNS := TArray < TArray < Integer >>
.Create(TArray<Integer>.Create(3, 2, 1, 1), // 0
TArray<Integer>.Create(2, 2, 2, 1), // 1
TArray<Integer>.Create(2, 1, 2, 2), // 2
TArray<Integer>.Create(1, 4, 1, 1), // 3
TArray<Integer>.Create(1, 1, 3, 2), // 4
TArray<Integer>.Create(1, 2, 3, 1), // 5
TArray<Integer>.Create(1, 1, 1, 4), // 6
TArray<Integer>.Create(1, 3, 1, 2), // 7
TArray<Integer>.Create(1, 2, 1, 3), // 8
TArray<Integer>.Create(3, 1, 1, 2) // 9
);
L_AND_G_PATTERNS := L_PATTERNS;
SetLength(L_AND_G_PATTERNS, 20);
// Move(L_PATTERNS[0], L_AND_G_PATTERNS[0], 10);
for i := 10 to Pred(20) do
begin
widths := L_PATTERNS[(i - 10)];
reversedWidths := TArray<Integer>.Create();
SetLength(reversedWidths, Length(widths));
for j := 0 to Pred(Length(widths)) do
reversedWidths[j] := widths[((Length(widths) - j) - 1)];
L_AND_G_PATTERNS[i] := reversedWidths;
end;
end;
class procedure TUPCEANReader.FinalizeClass;
begin
START_END_PATTERN := nil;
MIDDLE_PATTERN := nil;
L_PATTERNS := nil;
L_AND_G_PATTERNS := nil;
end;
class function TUPCEANReader.checkChecksum(const s: String): Boolean;
begin
Result := checkStandardUPCEANChecksum(s);
end;
class function TUPCEANReader.checkStandardUPCEANChecksum
(const s: String): Boolean;
var
len, sum, i, digit, ZeroInt: Integer;
begin
{$ZEROBASEDSTRINGS ON}
Result := false;
len := Length(s);
if (len = 0) then
exit;
sum := 0;
ZeroInt := Ord('0');
i := (len - 2);
while ((i >= 0)) do
begin
digit := Ord(s[i]) - ZeroInt;
if ((digit < 0) or (digit > 9)) then
exit;
Inc(sum, digit);
Dec(i, 2);
end;
sum := (sum * 3);
i := (len - 1);
while ((i >= 0)) do
begin
digit := Ord(s[i]) - ZeroInt;
if ((digit < 0) or (digit > 9)) then
exit;
Inc(sum, digit);
Dec(i, 2);
end;
{$ZEROBASEDSTRINGS OFF}
Result := ((sum mod 10) = 0);
end;
class function TUPCEANReader.decodeEnd(const row: IBitArray;
const endStart: Integer): TArray<Integer>;
begin
Result := findGuardPattern(row, endStart, false, START_END_PATTERN);
end;
class function TUPCEANReader.findStartGuardPattern(const row: IBitArray)
: TArray<Integer>;
var
foundStart: Boolean;
startRange, counters: TArray<Integer>;
nextStart, start, quietStart, idx, l: Integer;
begin
Result := nil;
foundStart := false;
startRange := nil;
nextStart := 0;
counters := TArray<Integer>.Create();
l := Length(START_END_PATTERN);
SetLength(counters, l);
while (not foundStart) do
begin
for idx := 0 to l - 1 do
counters[idx] := 0;
startRange := findGuardPattern(row, nextStart, false, START_END_PATTERN,
counters);
if (startRange = nil) then
exit;
start := startRange[0];
nextStart := startRange[1];
// Make sure there is a quiet zone at least as big as the start pattern before the barcode.
// If this check would run off the left edge of the image, do not accept this barcode,
// as it is very likely to be a false positive.
quietStart := start - (nextStart - start);
if (quietStart >= 0) then
foundStart := row.isRange(quietStart, start, false);
end;
counters:=nil;
Result := startRange;
end;
class function TUPCEANReader.findGuardPattern(const row: IBitArray;
const rowOffset: Integer; const whiteFirst: Boolean;
const pattern: TArray<Integer>): TArray<Integer>;
var
counters: TArray<Integer>;
begin
counters := TArray<Integer>.Create();
SetLength(counters, Length(pattern));
Result := findGuardPattern(row, rowOffset, whiteFirst, pattern, counters);
counters:=nil;
end;
class function TUPCEANReader.findGuardPattern(const row: IBitArray;
rowOffset: Integer; const whiteFirst: Boolean; const pattern: TArray<Integer>;
counters: TArray<Integer>): TArray<Integer>;
var
patternLength, width, x: Integer;
isWhite: Boolean;
counterPosition, patternStart: Integer;
curCounter: TArray<Integer>;
begin
Result := nil;
patternLength := Length(pattern);
width := row.Size;
isWhite := whiteFirst;
if whiteFirst then
rowOffset := row.getNextUnset(rowOffset)
else
rowOffset := row.getNextSet(rowOffset);
counterPosition := 0;
patternStart := rowOffset;
for x := rowOffset to Pred(width) do
begin
if (row[x] xor isWhite) then
Inc(counters[counterPosition])
else
begin
if (counterPosition = (patternLength - 1)) then
begin
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) <
MAX_AVG_VARIANCE) then
begin
Result := TArray<Integer>.Create(patternStart, x);
break;
end;
Inc(patternStart, (counters[0] + counters[1]));
curCounter := TArray<Integer>.Create();
SetLength(curCounter, Length(counters));
TArray.Copy<Integer>(counters, curCounter, 2, 0, (patternLength - 2));
{ curCounter[patternLength - 2] := 0;
curCounter[patternLength - 1] := 0; }
counters := curCounter;
Dec(counterPosition);
end
else
Inc(counterPosition);
counters[counterPosition] := 1;
isWhite := not isWhite;
end;
end;
end;
class function TUPCEANReader.decodeDigit(const row: IBitArray;
const counters: TArray<Integer>; const rowOffset: Integer;
const patterns: TArray<TArray<Integer>>; var digit: Integer): Boolean;
var
bestVariance, variance, i, max: Integer;
pattern: TArray<Integer>;
begin
Result := false;
digit := -1;
if (not TOneDReader.recordPattern(row, rowOffset, counters)) then
exit;
bestVariance := TUPCEANReader.MAX_AVG_VARIANCE; // worst variance we'll accept
max := Length(patterns);
for i := 0 to Pred(max) do
begin
pattern := patterns[i];
variance := TOneDReader.patternMatchVariance(counters, pattern,
TUPCEANReader.MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance) then
begin
bestVariance := variance;
digit := i;
end;
end;
Result := (digit >= 0);
end;
function TUPCEANReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
var
startRange: TArray<Integer>;
begin
startRange := findStartGuardPattern(row);
if startRange = nil then
exit(nil);
try
Result := DoDecodeRow(rowNumber, row, startRange, hints);
except
result := nil;
end;
end;
function TUPCEANReader.DoDecodeRow(const rowNumber: Integer;
const row: IBitArray;
const startGuardRange: TArray<Integer>;
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
var
len: Integer;
res: TStringBuilder;
resultString: String;
endStart, ending, quietEnd: Integer;
endRange: TArray<Integer>;
resultPoints: TArray<IResultPoint>;
left, right: Single;
resultPointCallback: TResultPointCallback;
obj: TObject;
resPoint: IResultPoint;
decodeResult, extensionResult: TReadResult;
format: TBarcodeFormat;
allowedExtensions: TArray<Integer>;
valid: Boolean;
extensionLength: Integer;
countryID: String;
begin
Result := nil;
resultPointCallback := nil;
if ((hints <> nil) and
(hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK))) then
begin
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
if (obj is TResultPointEventObject) then
resultPointCallback := TResultPointEventObject(obj).Event;
end;
res := decodeRowStringBuffer;
res.Length := 0;
endStart := DecodeMiddle(row, startGuardRange, res);
if (endStart < 0) then
exit;
if Assigned(resultPointCallback) then
begin
resPoint := TResultPointHelpers.CreateResultPoint(endStart, rowNumber);
resultPointCallback(resPoint);
end;
endRange := decodeEnd(row, endStart);
if (endRange = nil) then
exit;
if Assigned(resultPointCallback) then
begin
resPoint := TResultPointHelpers.CreateResultPoint
((endRange[0] + endRange[1]) div 2, rowNumber);
resultPointCallback(resPoint);
end;
ending := endRange[1];
quietEnd := (ending + (ending - endRange[0]));
if ((quietEnd >= row.Size) or not row.isRange(ending, quietEnd, false)) then
exit;
resultString := res.ToString;
if (Length(resultString) < 8) then
exit;
if (not checkChecksum(resultString)) then
exit;
left := ((startGuardRange[1] + startGuardRange[0]) div 2);
right := ((endRange[1] + endRange[0]) div 2);
format := BarcodeFormat;
resultPoints := TArray<IResultPoint>.Create
(TResultPointHelpers.CreateResultPoint(left, rowNumber),
TResultPointHelpers.CreateResultPoint(right, rowNumber));
decodeResult := TReadResult.Create(resultString, nil, resultPoints, format);
extensionResult := extensionReader.decodeRow(rowNumber, row, endRange[1]);
if (extensionResult <> nil) then
begin
decodeResult.putMetadata(TResultMetadataType.UPC_EAN_EXTENSION,
TResultMetaData.CreateStringMetadata(extensionResult.Text));
decodeResult.putAllMetadata(extensionResult.ResultMetadata);
decodeResult.addResultPoints(extensionResult.resultPoints);
extensionLength := Length(extensionResult.Text);
extensionResult.Free;
if (hints <> nil) and
(hints.ContainsKey(TDecodeHintType.ALLOWED_EAN_EXTENSIONS)) then
allowedExtensions := TArray<Integer>
(hints[TDecodeHintType.ALLOWED_EAN_EXTENSIONS])
else
allowedExtensions := nil;
if (allowedExtensions <> nil) then
begin
valid := false;
for len in allowedExtensions do
begin
if (extensionLength = len) then
begin
valid := true;
break;
end;
end;
if (not valid) then
exit;
end
end;
case format of
TBarcodeFormat.EAN_13, TBarcodeFormat.UPC_A:
begin
countryID := eanManSupport.lookupCountryIdentifier(resultString);
if (countryID <> '') then
decodeResult.putMetadata(TResultMetadataType.POSSIBLE_COUNTRY,
TResultMetaData.CreateStringMetadata(countryID));
end;
end;
Result := decodeResult;
end;
initialization
TUPCEANReader.InitializeClass;
finalization
TUPCEANReader.FinalizeClass;
end.
@@ -0,0 +1,235 @@
unit ZXing.OneD.UPCEReader;
{
* Copyright 2008 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
}
interface
uses
System.SysUtils,
System.Generics.Collections,
Math,
ZXing.OneD.OneDReader,
ZXing.Common.BitArray,
ZXing.BinaryBitmap,
ZXing.ReadResult,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.BarcodeFormat,
ZXing.Helpers,
ZXing.OneD.EAN13Reader,
ZXing.OneD.UPCEANReader;
type
T2DIntArray = TArray<TArray<Integer>>;
TUPCEReader = class(TUPCEANReader)
private
class var decodeMiddleCounters: TArray<Integer>;
class var MIDDLE_END_PATTERN: TArray<Integer>;
class var NUMSYS_AND_CHECK_DIGIT_PATTERNS: T2DIntArray;
class procedure DoInitialize();
class procedure DoFinalize();
class function determineNumSysAndCheckDigit(resultString: TStringBuilder;
lgPatternFound: Integer): boolean; static;
protected
class function checkChecksum(const s: string): boolean; override;
class function decodeEnd(const row: IBitArray; const endStart: Integer)
: TArray<Integer>; override;
public
class function DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const res: TStringBuilder)
: Integer; override;
public
class function convertUPCEtoUPCA(upce: string): string; static;
function BarcodeFormat: TBarcodeFormat; override;
end;
implementation
{ TUPCEReader }
class function TUPCEReader.determineNumSysAndCheckDigit
(resultString: TStringBuilder; lgPatternFound: Integer): boolean;
var
numSys, d: Integer;
begin
numSys := 0;
while ((numSys <= 1)) do
begin
d := 0;
while ((d < 10)) do
begin
if (lgPatternFound = TUPCEReader.NUMSYS_AND_CHECK_DIGIT_PATTERNS
[numSys][d]) then
begin
resultString.Insert(0, Char($30 + numSys) );
resultString.Append(char($30 + d));
begin
Result := true;
exit
end
end;
inc(d)
end;
inc(numSys)
end;
Result := false;
end;
class function TUPCEReader.checkChecksum(const s: string): boolean;
begin
Result := inherited checkChecksum(TUPCEReader.convertUPCEtoUPCA(s))
end;
class function TUPCEReader.convertUPCEtoUPCA(upce: string): string;
var
upceChars: string;
res: TStringBuilder;
lastChar: char;
begin
upceChars := upce.Substring(1, 6);
res := TStringBuilder.Create(12);
res.Append(upce.Chars[0]);
lastChar := upceChars.Chars[5];
case lastChar of
'0', '1', '2':
begin
res.Append(upceChars, 0, 2);
res.Append(lastChar);
res.Append('0000');
res.Append(upceChars, 2, 3);
end;
'3':
begin
res.Append(upceChars, 0, 3);
res.Append('00000');
res.Append(upceChars, 3, 2);
end;
'4':
begin
res.Append(upceChars, 0, 4);
res.Append('00000');
res.Append(upceChars.Chars[4]);
end;
else
begin
res.Append(upceChars, 0, 5);
res.Append('0000');
res.Append(lastChar);
end;
end;
res.Append(upce.Chars[7]);
Result := res.ToString;
res.Free;
end;
class function TUPCEReader.decodeEnd(const row: IBitArray;
const endStart: Integer): TArray<Integer>;
begin
Result := TUPCEANReader.findGuardPattern(row, endStart, true,
TUPCEReader.MIDDLE_END_PATTERN);
end;
class function TUPCEReader.DecodeMiddle(const row: IBitArray;
const startRange: TArray<Integer>; const res: TStringBuilder): Integer;
var
bestMatch: Integer;
counter, ending, lgPatternFound, rowOffset, x: Integer;
counters: TArray<Integer>;
begin
counters := self.decodeMiddleCounters;
counters[0] := 0;
counters[1] := 0;
counters[2] := 0;
counters[3] := 0;
ending := row.Size;
rowOffset := startRange[1];
lgPatternFound := 0;
x := 0;
while ((x < 6) and (rowOffset < ending)) do
begin
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset,
TUPCEANReader.L_AND_G_PATTERNS, bestMatch)) then
begin
exit(-1);
end;
res.Append(Char(ord('0') + bestMatch mod 10));
for counter in counters do
begin
inc(rowOffset, counter)
end;
if (bestMatch >= 10) then
lgPatternFound := lgPatternFound or (1 shl (5 - x));
inc(x)
end;
if (not TUPCEReader.determineNumSysAndCheckDigit(res, lgPatternFound)) then
begin
exit(-1);
end;
Result := rowOffset;
end;
function TUPCEReader.BarcodeFormat: TBarcodeFormat;
begin
Result := TBarcodeFormat.UPC_E;
end;
class procedure TUPCEReader.DoInitialize;
begin
MIDDLE_END_PATTERN := TArray<Integer>.Create(1, 1, 1, 1, 1, 1);
NUMSYS_AND_CHECK_DIGIT_PATTERNS :=
T2DIntArray.Create(TArray<Integer>.Create($38, $34, 50, $31, $2C, $26, $23,
$2A, $29, $25), TArray<Integer>.Create(7, 11, 13, 14, $13, $19, $1C, $15,
$16, $1A));
decodeMiddleCounters := TArray<Integer>.Create(0,0,0,0);
end;
class procedure TUPCEReader.DoFinalize;
begin
MIDDLE_END_PATTERN := nil;
NUMSYS_AND_CHECK_DIGIT_PATTERNS := nil;
decodeMiddleCounters := nil;
end;
initialization
TUPCEReader.DoInitialize;
finalization
TUPCEReader.DoFinalize;
end.