dd9fd62dcb
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>
524 lines
12 KiB
ObjectPascal
524 lines
12 KiB
ObjectPascal
{
|
|
* 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.
|