Files
r-zakarya dd9fd62dcb 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>
2026-09-17 18:38:41 +01:00

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.