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:
@@ -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.
|
||||
Reference in New Issue
Block a user