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

593 lines
19 KiB
ObjectPascal

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