FastReport_2022_VCL/Source/frxDelphiZXIngAztecCode.pas
2024-01-01 16:13:08 +01:00

1152 lines
32 KiB
ObjectPascal

(*
* Copyright 2013 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.ext.zawq.ru/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.
*)
unit frxDelphiZXIngAztecCode;
interface
uses frxBarcode2DBase;
{$I frx.inc}
const
DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
type
TAztecEncoder = class
private
FData: WideString;
FMatrixSize: integer;
FMinECCPercent: integer;
function GetIsBlack(Row, Column: integer): Boolean;
procedure SetData(const Value: WideString);
procedure SetMinECCPercent(const Value: integer);
protected
procedure Update;
public
FElements: T2DBooleanArray;
constructor Create;
property IsBlack[Row, Column: integer]: Boolean read GetIsBlack;
property Data: WideString read FData write SetData;
property MatrixSize: integer read FMatrixSize;
property MinECCPercent: integer read FMinECCPercent write SetMinECCPercent;
end;
implementation
uses
{$IFNDEF FPC}Windows,{$ENDIF} Math, Classes, SysUtils, Contnrs,
frxDelphiZXIngCode, frxUnicodeUtils;
const
DEFAULT_AZTEC_LAYERS = 0;
MAX_NB_BITS = 32;
MAX_NB_BITS_COMPACT = 4;
MaxLayers = 32;
WORD_SIZE: array [0 .. MaxLayers] of integer = (4, 6, 6, 8, 8, 8, 8, 8, 8, 10,
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12);
type
TEncoder = class
private
FMatrixSize: integer;
FMatrix: array of array of Boolean;
FCompact: Boolean;
FLayers: integer;
FCodeWords: integer;
function GetMatrix(x, y: integer): Boolean;
protected
FGarbage: TObjectList;
procedure SetBlack(x, y: integer);
procedure DrawBullsEye(Center, Size: LongInt);
function GenerateModeMessage: TBitArray;
procedure DrawModeMessage(MatrixSize: LongInt; ModeMessage: TBitArray);
function GenerateCheckWords(BitArray: TBitArray;
TotalBits, WordSize: integer): TBitArray;
function BitsToWords(StuffedBits: TBitArray; WordSize, TotalWords: integer)
: TIntegerArray;
function GetGF(WordSize: integer): TGenericGF;
function StuffBits(Bits: TBitArray; WordSize: integer): TBitArray;
function TotalBitsInLayer: integer;
public
destructor Destroy; override;
// Encodes the given binary content as an Aztec symbol
// minECCPercent: minimal percentage of error check words (According to ISO/IEC 24778:2008, a minimum of 23% + 3 words is recommended)
// userSpecifiedLayers: if non-zero, a user-specified value for the number of layers
// Returns Aztec symbol matrix with metadata
procedure Encode(Data: AnsiString;
MinECCPercent: LongInt = DEFAULT_EC_PERCENT;
UserSpecifiedLayers: LongInt = DEFAULT_AZTEC_LAYERS);
property MatrixSize: integer read FMatrixSize;
property Matrix[x, y: integer]: Boolean read GetMatrix;
property Compact: Boolean read FCompact;
property Layers: integer read FLayers;
property CodeWords: integer read FCodeWords;
end;
{ TAztecEncoder }
constructor TAztecEncoder.Create;
begin
FData := '';
FMatrixSize := 0;
end;
function TAztecEncoder.GetIsBlack(Row, Column: integer): Boolean;
begin
Result := FElements[Row, Column];
end;
procedure TAztecEncoder.SetData(const Value: WideString);
begin
if (FData <> Value) then
begin
FData := Value;
Update;
end;
end;
procedure TAztecEncoder.SetMinECCPercent(const Value: integer);
begin
if (FMinECCPercent <> Value) then
begin
FMinECCPercent := Value;
Update;
end;
end;
procedure TAztecEncoder.Update;
var
AnsiSt: AnsiString;
Encoder: TEncoder;
w, h: integer;
begin
{$IFDEF Delphi12}
AnsiSt := _UnicodeToAnsi(FData, DEFAULT_CHARSET, 28591); // codepage 28591 = ISO-8859-1
{$ELSE}
AnsiSt := AnsiString(FData);
{$ENDIF}
Encoder := TEncoder.Create;
Encoder.Encode(AnsiSt, FMinECCPercent);
FMatrixSize := Encoder.MatrixSize;
SetLength(FElements, MatrixSize, MatrixSize);
for w := 0 to MatrixSize - 1 do
for h := 0 to MatrixSize - 1 do
FElements[w, h] := Encoder.Matrix[w, h];
Encoder.Free;
end;
(******************************************************************************)
type
TState = class;
THighLevelEncoder = class
private
function UpdateStateListForChar(States: TList; Index: integer): TList;
procedure UpdateStateForChar(State: TState; Index: integer; Result: TList);
function UpdateStateListForPair(States: TList;
Index, PairCode: integer): TList;
procedure UpdateStateForPair(State: TState; Index, PairCode: integer;
Result: TList);
function SimplifyStates(States: TList): TList;
protected
FText: AnsiString;
FGarbage: TObjectList;
public
constructor Create(AGarbage: TObjectList; AText: AnsiString);
function Encode: TBitArray;
end;
TToken = class
private
FPrevious: TToken;
FGarbage: TObjectList;
public
constructor Create(AGarbage: TObjectList; APrevious: TToken);
class function EMPTY(AGarbage: TObjectList): TToken;
function Add(AValue, ABitCount: integer): TToken;
function AddBinaryShift(Start, ByteCount: integer): TToken;
procedure AppendTo(BitArray: TBitArray; Text: AnsiString); virtual;
abstract;
property Previous: TToken read FPrevious;
property Garbage: TObjectList read FGarbage;
end;
TState = class
private
FMode: LongInt;
FToken: TToken;
FBinaryShiftByteCount: LongInt;
FBitCount: LongInt;
public
class function INITIAL_STATE(AGarbage: TObjectList): TState;
constructor Create(AToken: TToken; AMode, ABinaryBytes, ABitCount: LongInt);
function LatchAndAppend(NewMode, Value: LongInt): TState;
function ShiftAndAppend(NewMode, Value: LongInt): TState;
function AddBinaryShiftChar(Index: LongInt): TState;
function EndBinaryShift(Index: LongInt): TState;
function IsBetterThanOrEqualTo(Other: TState): Boolean;
function ToBitArray(Text: AnsiString): TBitArray;
function ToString: string; {$IFDEF Delphi12} override; {$ENDIF}
property Mode: LongInt read FMode;
property BitCount: LongInt read FBitCount;
property BinaryShiftByteCount: LongInt read FBinaryShiftByteCount;
end;
const
MODE_UPPER = 0; // 5 bits
MODE_LOWER = 1; // 5 bits
MODE_DIGIT = 2; // 4 bits
MODE_MIXED = 3; // 5 bits
MODE_PUNCT = 4; // 5 bits
ShiftTableSize = 6;
type
TMode = MODE_UPPER .. MODE_PUNCT;
const
MODE_NAMES: array [TMode] of string = ('UPPER', 'LOWER', 'DIGIT',
'MIXED', 'PUNCT');
LATCH_TABLE: array [TMode, TMode] of integer = ((0, //
5 shl 16 + 28, // UPPER -> LOWER
5 shl 16 + 30, // UPPER -> DIGIT
5 shl 16 + 29, // UPPER -> MIXED
10 shl 16 + 29 shl 5 + 30 // UPPER -> MIXED -> PUNCT
), (9 shl 16 + 30 shl 4 + 14, // LOWER -> DIGIT -> UPPER
0, //
5 shl 16 + 30, // LOWER -> DIGIT
5 shl 16 + 29, // LOWER -> MIXED
10 shl 16 + 29 shl 5 + 30 // LOWER -> MIXED -> PUNCT
), (4 shl 16 + 14, // DIGIT -> UPPER
9 shl 16 + 14 shl 5 + 28, // DIGIT -> UPPER -> LOWER
0, //
9 shl 16 + 14 shl 5 + 29, // DIGIT -> UPPER -> MIXED
14 shl 16 + 14 shl 10 + 29 shl 5 + 30 // DIGIT -> UPPER -> MIXED -> PUNCT
), (5 shl 16 + 29, // MIXED -> UPPER
5 shl 16 + 28, // MIXED -> LOWER
10 shl 16 + 29 shl 5 + 30, // MIXED -> UPPER -> DIGIT
0, //
5 shl 16 + 30 // MIXED -> PUNCT
), (5 shl 16 + 31, // PUNCT -> UPPER
10 shl 16 + 31 shl 5 + 28, // PUNCT -> UPPER -> LOWER
10 shl 16 + 31 shl 5 + 30, // PUNCT -> UPPER -> DIGIT
10 shl 16 + 31 shl 5 + 29, // PUNCT -> UPPER -> MIXED
0 //
));
var
CHAR_MAP: array [TMode, AnsiChar] of integer;
SHIFT_TABLE: array [0 .. ShiftTableSize, 0 .. ShiftTableSize] of integer;
type
TSimpleToken = class(TToken)
private
FValue: SmallInt;
FBitCount: SmallInt;
public
constructor Create(AGarbage: TObjectList; APrevious: TToken; AValue, ABitCount: integer);
// function ToString: string;
procedure AppendTo(BitArray: TBitArray; Text: AnsiString); override;
end;
TBinaryShiftToken = class(TToken)
private
FBinaryShiftStart: SmallInt;
FBinaryShiftByteCount: SmallInt;
public
constructor Create(AGarbage: TObjectList; APrevious: TToken;
ABinaryShiftStart, ABinaryShiftByteCount: integer);
// function ToString: string;
procedure AppendTo(BitArray: TBitArray; Text: AnsiString); override;
end;
procedure TBinaryShiftToken.AppendTo(BitArray: TBitArray; Text: AnsiString);
var
i: integer;
begin
for i := 0 to FBinaryShiftByteCount - 1 do
begin
if (i = 0) or (i = 31) and (FBinaryShiftByteCount <= 62) then
begin
// We need a header before the first character, and before
// character 31 when the total byte code is <= 62
BitArray.AppendBits(31, 5); // BINARY_SHIFT
if FBinaryShiftByteCount > 62 then
BitArray.AppendBits(FBinaryShiftByteCount - 31, 16)
else if i = 0 then // 1 <= binaryShiftByteCode <= 62
BitArray.AppendBits(Min(FBinaryShiftByteCount, 31), 5)
else // 32 <= binaryShiftCount <= 62 and i == 31
BitArray.AppendBits(FBinaryShiftByteCount - 31, 5);
end;
BitArray.AppendBits(Ord(Text[FBinaryShiftStart + i]), 8);
end;
end;
constructor TBinaryShiftToken.Create(AGarbage: TObjectList; APrevious: TToken;
ABinaryShiftStart, ABinaryShiftByteCount: integer);
begin
inherited Create(AGarbage, APrevious);
FBinaryShiftStart := ABinaryShiftStart;
FBinaryShiftByteCount := ABinaryShiftByteCount;
end;
//function TBinaryShiftToken.ToString: string;
//begin
// Result := Format('<%d::%d>', [FBinaryShiftStart, FBinaryShiftStart + FBinaryShiftByteCount - 1]);
//end;
{ TSimpleToken }
procedure TSimpleToken.AppendTo(BitArray: TBitArray; Text: AnsiString);
begin
BitArray.AppendBits(FValue, FBitCount)
end;
constructor TSimpleToken.Create(AGarbage: TObjectList; APrevious: TToken; AValue, ABitCount: integer);
begin
inherited Create(AGarbage, APrevious);
FValue := AValue;
FBitCount := ABitCount;
end;
//function TSimpleToken.ToString: string;
//var
// Value: LongInt;
//begin
// Value := FValue and ((1 shl FBitCount) - 1);
// Value := Value or (1 shl FBitCount);
// Result := Format('<%s>', [ToBinaryString(Value or (1 shl FBitCount) and $7fffffff)]);
//end;
{ TToken }
function TToken.Add(AValue, ABitCount: integer): TToken;
begin
Result := TSimpleToken.Create(Garbage, Self, AValue, ABitCount);
end;
function TToken.AddBinaryShift(Start, ByteCount: integer): TToken;
begin
Result := TBinaryShiftToken.Create(Garbage, Self, Start, ByteCount)
end;
constructor TToken.Create(AGarbage: TObjectList; APrevious: TToken);
begin
FPrevious := APrevious;
FGarbage := AGarbage;
if Assigned(Garbage) then
Garbage.Add(Self);
end;
class function TToken.EMPTY(AGarbage: TObjectList): TToken;
begin
Result := TSimpleToken.Create(AGarbage, nil, 0, 0);
end;
{ TState }
function TState.AddBinaryShiftChar(Index: LongInt): TState;
var
Latch, LocalBitCount, LocalMode, DeltaBitCount: LongInt;
LocalToken: TToken;
begin
LocalToken := FToken;
LocalMode := Mode;
LocalBitCount := BitCount;
if Mode in [MODE_PUNCT, MODE_DIGIT] then
begin
//assert binaryShiftByteCount == 0;
Latch := LATCH_TABLE[LocalMode][MODE_UPPER];
LocalToken := LocalToken.Add(Latch and $FFFF, Latch shr 16);
LocalBitCount := LocalBitCount + Latch shr 16;
LocalMode := MODE_UPPER;
end;
DeltaBitCount := IfValue(BinaryShiftByteCount in [0, 31], 18,
IfValue(BinaryShiftByteCount = 62, 9, 8));
Result := TState.Create(LocalToken, LocalMode, BinaryShiftByteCount + 1,
LocalBitCount + DeltaBitCount);
if Result.BinaryShiftByteCount = 2047 + 31 then
// The string is as long as it's allowed to be. We should end it.
Result.EndBinaryShift(Index + 1);
end;
constructor TState.Create(AToken: TToken;
AMode, ABinaryBytes, ABitCount: LongInt);
begin
FToken := AToken;
FMode := AMode;
FBinaryShiftByteCount := ABinaryBytes;
FBitCount := ABitCount;
if Assigned(FToken.Garbage) then
FToken.Garbage.Add(Self);
// Make sure we match the token
//int binaryShiftBitCount = (binaryShiftByteCount * 8) +
// (binaryShiftByteCount == 0 ? 0 :
// binaryShiftByteCount <= 31 ? 10 :
// binaryShiftByteCount <= 62 ? 20 : 21);
//assert this.bitCount == token.getTotalBitCount() + binaryShiftBitCount;
end;
function TState.EndBinaryShift(Index: LongInt): TState;
var
LocalToken: TToken;
begin
if BinaryShiftByteCount = 0 then
Result := Self
else
begin
LocalToken := FToken.AddBinaryShift(Index - BinaryShiftByteCount,
BinaryShiftByteCount);
//assert token.getTotalBitCount() == this.bitCount;
Result := TState.Create(LocalToken, Mode, 0, BitCount);
end;
end;
class function TState.INITIAL_STATE(AGarbage: TObjectList): TState;
begin
Result := TState.Create(TToken.EMPTY(AGarbage), MODE_UPPER, 0, 0);
end;
function TState.IsBetterThanOrEqualTo(Other: TState): Boolean;
var
MySize: LongInt;
begin
MySize := BitCount + (LATCH_TABLE[Mode][Other.Mode] shr 16);
if (Other.BinaryShiftByteCount > 0) and
((BinaryShiftByteCount = 0) or
(BinaryShiftByteCount > Other.BinaryShiftByteCount)) then
MySize := MySize + 10; // Cost of entering Binary Shift mode.
Result := MySize <= Other.BitCount;
end;
function TState.LatchAndAppend(NewMode, Value: LongInt): TState;
var
Latch, LocalBitCount, LatchModeBitCount: LongInt;
LocalToken: TToken;
begin
//assert binaryShiftByteCount == 0;
LocalBitCount := BitCount;
LocalToken := FToken;
if NewMode <> Mode then
begin
Latch := LATCH_TABLE[Mode][NewMode];
LocalToken := LocalToken.Add(Latch and $FFFF, Latch shr 16);
LocalBitCount := LocalBitCount + Latch shr 16;
end;
LatchModeBitCount := IfValue(NewMode = MODE_DIGIT, 4, 5);
LocalToken := LocalToken.Add(Value, LatchModeBitCount);
Result := TState.Create(LocalToken, NewMode, 0,
LocalBitCount + LatchModeBitCount);
end;
function TState.ShiftAndAppend(NewMode, Value: LongInt): TState;
var
ThisModeBitCount: LongInt;
LocalToken: TToken;
begin
//assert binaryShiftByteCount == 0 && this.mode != mode;
LocalToken := FToken;
ThisModeBitCount := IfValue(Mode = MODE_DIGIT, 4, 5);
// Shifts exist only to UPPER and PUNCT, both with tokens size 5.
LocalToken := LocalToken.Add(SHIFT_TABLE[Mode][NewMode], ThisModeBitCount);
LocalToken := LocalToken.Add(Value, 5);
Result := TState.Create(LocalToken, Mode, 0, BitCount + ThisModeBitCount + 5);
end;
function TState.ToBitArray(Text: AnsiString): TBitArray;
var
Symbols: TList;
LocalToken: TToken;
i: LongInt;
begin
// Reverse the tokens, so that they are in the order that they should be output
Symbols := TList.Create;
LocalToken := EndBinaryShift(Length(Text)).FToken;
while LocalToken <> nil do
begin
Symbols.Insert(0, LocalToken);
LocalToken := LocalToken.Previous;
end;
Result := TBitArray.Create;
for i := 0 to Symbols.Count - 1 do
TToken(Symbols[i]).AppendTo(Result, Text);
Symbols.Free;
end;
function TState.ToString: string;
begin
Result := Format('%s bits=%d bytes=%d', [MODE_NAMES[mode], BitCount, BinaryShiftByteCount]);
end;
{ THighLevelEncoder }
constructor THighLevelEncoder.Create(AGarbage: TObjectList; AText: AnsiString);
begin
FText := AText;
FGarbage := AGarbage;
end;
function THighLevelEncoder.Encode: TBitArray;
procedure FreeAndSet(var List1: TList; List2: TList);
var
Temp: TList;
begin
Temp := List2;
List1.Free;
List1 := Temp;
end;
var
States: TList;
Index, PairCode, i: integer;
NextChar: AnsiChar;
MinState, State: TState;
begin
States := TList.Create;
States.Add(TState.INITIAL_STATE(FGarbage));
Index := 1;
while Index <= Length(FText) do
begin
NextChar := IfValue(Index < Length(FText), FText[Index + 1], #0);
case FText[Index] of
#13:
PairCode := IfValue(NextChar = #10, 2, 0);
'.':
PairCode := IfValue(NextChar = ' ', 3, 0);
',':
PairCode := IfValue(NextChar = ' ', 4, 0);
':':
PairCode := IfValue(NextChar = ' ', 5, 0);
else
PairCode := 0;
end;
if PairCode > 0 then
begin
FreeAndSet(States, UpdateStateListForPair(States, Index, PairCode));
Inc(Index);
end
else
FreeAndSet(States, UpdateStateListForChar(States, Index));
Inc(Index);
end;
MinState := nil;
for i := 0 to States.Count - 1 do
begin
State := TState(States[i]);
if (MinState = nil) or (State.BitCount < MinState.BitCount) then
MinState := State;
end;
Result := MinState.ToBitArray(FText);
States.Free;
end;
function THighLevelEncoder.SimplifyStates(States: TList): TList;
var
Add: Boolean;
i, j, Index: integer;
NewState, OldState: TState;
RemoveList: TList;
begin
RemoveList := TList.Create;
Result := TList.Create;
for i := 0 to States.Count - 1 do
begin
NewState := TState(States[i]);
Add := True;
RemoveList.Clear;
for j := 0 to Result.Count - 1 do
begin
OldState := TState(Result[j]);
if OldState.IsBetterThanOrEqualTo(NewState) then
begin
Add := False;
Break;
end;
if NewState.IsBetterThanOrEqualTo(OldState) then
RemoveList.Add(OldState);
end;
if Add then
Result.Add(NewState);
for j := 0 to RemoveList.Count - 1 do
begin
Index := Result.IndexOf(RemoveList[j]);
if Index > -1 then
Result.Delete(Index);
end;
end;
RemoveList.Free;
end;
procedure THighLevelEncoder.UpdateStateForChar(State: TState; Index: integer;
Result: TList);
var
ch: AnsiChar;
CharInCurrentTable: Boolean;
StateNoBinary, latch_state, shift_state, binaryState: TState;
Mode: TMode;
CharInMode: integer;
begin
ch := FText[Index];
CharInCurrentTable := CHAR_MAP[State.Mode][ch] > 0;
StateNoBinary := nil;
for Mode := MODE_UPPER to MODE_PUNCT do
begin
CharInMode := CHAR_MAP[Mode][ch];
if CharInMode > 0 then
begin
if StateNoBinary = nil then
// Only create stateNoBinary the first time it's required.
StateNoBinary := State.EndBinaryShift(Index);
if ((not CharInCurrentTable) or (Mode = State.Mode)) or (Mode = MODE_DIGIT)
then
begin
latch_state := StateNoBinary.LatchAndAppend(Mode, CharInMode);
Result.Add(latch_state);
end;
if (not CharInCurrentTable) and (SHIFT_TABLE[State.Mode][Mode] >= 0) then
begin
shift_state := StateNoBinary.ShiftAndAppend(Mode, CharInMode);
Result.Add(shift_state);
end;
end;
end;
if (State.BinaryShiftByteCount > 0) or (CHAR_MAP[State.Mode][ch] = 0) then
begin
binaryState := State.AddBinaryShiftChar(Index);
Result.Add(binaryState);
end
end;
procedure THighLevelEncoder.UpdateStateForPair(State: TState;
Index, PairCode: integer; Result: TList);
var
StateNoBinary, digit_state, binaryState: TState;
begin
StateNoBinary := State.EndBinaryShift(Index);
// Possibility 1. Latch to MODE_PUNCT, and then append this code
Result.Add(StateNoBinary.LatchAndAppend(MODE_PUNCT, PairCode));
if State.Mode <> MODE_PUNCT then
// Possibility 2. Shift to MODE_PUNCT, and then append this code.
// Every state except MODE_PUNCT (handled above) can shift
Result.Add(StateNoBinary.ShiftAndAppend(MODE_PUNCT, PairCode));
if PairCode in [3 .. 4] then
begin
// both characters are in DIGITS. Sometimes better to just add two digits
// period or comma in DIGIT
digit_state := StateNoBinary.LatchAndAppend(MODE_DIGIT, 16 - PairCode)
.LatchAndAppend(MODE_DIGIT, 1);
// space in DIGIT
Result.Add(digit_state);
end;
if State.BinaryShiftByteCount > 0 then
begin
// It only makes sense to do the characters as binary if we're already
// in binary mode.
binaryState := State.AddBinaryShiftChar(Index)
.AddBinaryShiftChar(Index + 1);
Result.Add(binaryState);
end
end;
function THighLevelEncoder.UpdateStateListForChar(States: TList;
Index: integer): TList;
var
i: integer;
State: TState;
Complex: TList;
begin
Complex := TList.Create;
for i := 0 to States.Count - 1 do
begin
State := TState(States[i]);
UpdateStateForChar(State, Index, Complex);
end;
Result := SimplifyStates(Complex);
Complex.Free;
end;
function THighLevelEncoder.UpdateStateListForPair(States: TList;
Index, PairCode: integer): TList;
var
i: integer;
State: TState;
Complex: TList;
begin
Complex := TList.Create;
for i := 0 to States.Count - 1 do
begin
State := TState(States[i]);
UpdateStateForPair(State, Index, PairCode, Complex);
end;
Result := SimplifyStates(Complex);
Complex.Free;
end;
{ TEncoder }
function TEncoder.BitsToWords(StuffedBits: TBitArray;
WordSize, TotalWords: integer): TIntegerArray;
var
i, j, N: integer;
Value: LongInt;
begin
SetLength(Result, TotalWords);
N := StuffedBits.Size div WordSize;
for i := 0 to N - 1 do
begin
Value := 0;
for j := 0 to WordSize - 1 do
Value := Value or IfValue(StuffedBits[i * WordSize + j],
1 shl (WordSize - j - 1), 0);
Result[i] := Value;
end;
end;
destructor TEncoder.Destroy;
begin
Finalize(FMatrix);
inherited;
end;
procedure TEncoder.DrawBullsEye(Center, Size: integer);
var
i, j: LongInt;
begin
i := 0;
while i < Size do
begin
for j := Center - i to Center + i do
begin
SetBlack(j, Center - i);
SetBlack(j, Center + i);
SetBlack(Center - i, j);
SetBlack(Center + i, j);
end;
i := i + 2;
end;
SetBlack(Center - Size, Center - Size);
SetBlack(Center - Size + 1, Center - Size);
SetBlack(Center - Size, Center - Size + 1);
SetBlack(Center + Size, Center - Size);
SetBlack(Center + Size, Center - Size + 1);
SetBlack(Center + Size, Center + Size - 1);
end;
procedure TEncoder.DrawModeMessage(MatrixSize: LongInt; ModeMessage: TBitArray);
var
i, Center, Offset: LongInt;
begin
Center := MatrixSize div 2;
if Compact then
for i := 0 to 7 - 1 do
begin
Offset := Center - 3 + i;
if ModeMessage[i] then
SetBlack(Offset, Center - 5);
if ModeMessage[i + 7] then
SetBlack(Center + 5, Offset);
if ModeMessage[20 - i] then
SetBlack(Offset, Center + 5);
if ModeMessage[27 - i] then
SetBlack(Center - 5, Offset);
end
else
for i := 0 to 10 - 1 do
begin
Offset := Center - 5 + i + i div 5;
if ModeMessage[i] then
SetBlack(Offset, Center - 7);
if ModeMessage[i + 10] then
SetBlack(Center + 7, Offset);
if ModeMessage[29 - i] then
SetBlack(Offset, Center + 7);
if ModeMessage[39 - i] then
SetBlack(Center - 7, Offset);
end;
end;
procedure TEncoder.Encode(Data: AnsiString;
MinECCPercent: LongInt = DEFAULT_EC_PERCENT;
UserSpecifiedLayers: LongInt = DEFAULT_AZTEC_LAYERS);
var
Bits, StuffedBits, MessageBits, ModeMessage: TBitArray;
HighLevelEncoder: THighLevelEncoder;
i, j, k, EccBits, TotalSizeBits, LocalTotalBitsInLayer, WordSize,
UsableBitsInLayers, BaseMatrixSize, OrigCenter, Center, NewOffset,
RowOffset, RowSize, ColumnOffset: LongInt;
AlignmentMap: TIntegerArray;
begin
FGarbage := TObjectList.Create;
// High-level encode
HighLevelEncoder := THighLevelEncoder.Create(FGarbage, Data);
Bits := HighLevelEncoder.Encode;
// stuff bits and choose symbol size
EccBits := Bits.Size * MinECCPercent div 100 + 11;
TotalSizeBits := Bits.Size + EccBits;
if UserSpecifiedLayers <> DEFAULT_AZTEC_LAYERS then
begin
FCompact := UserSpecifiedLayers < 0;
FLayers := Abs(UserSpecifiedLayers);
if Layers > IfValue(Compact, MAX_NB_BITS_COMPACT, MAX_NB_BITS) then
raise Exception.Create(Format('Illegal value %d for layers',
[UserSpecifiedLayers]));
LocalTotalBitsInLayer := TotalBitsInLayer;
WordSize := WORD_SIZE[Layers];
UsableBitsInLayers := LocalTotalBitsInLayer -
(LocalTotalBitsInLayer mod WordSize);
FreeAndSetBitArray(StuffedBits, StuffBits(Bits, WordSize));
if StuffedBits.Size + EccBits > UsableBitsInLayers then
raise Exception.Create('Data to large for user specified layer');
if Compact and (StuffedBits.Size > WordSize * 64) then
// Compact format only allows 64 data words, though C4 can hold more words than that
raise Exception.Create('Data to large for user specified layer');
end
else
begin
WordSize := 0;
StuffedBits := nil;
// We look at the possible table sizes in the order Compact1, Compact2, Compact3,
// Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
// is the same size, but has more data.
for i := 0 to MaxInt do
begin
if i > MAX_NB_BITS then
raise Exception.Create('Data too large for an Aztec code');
FCompact := i <= 3;
FLayers := IfValue(Compact, i + 1, i);
LocalTotalBitsInLayer := TotalBitsInLayer;
if TotalSizeBits > LocalTotalBitsInLayer then
Continue;
// [Re]stuff the bits if this is the first opportunity, or if the
// wordSize has changed
if WordSize <> WORD_SIZE[Layers] then
begin
WordSize := WORD_SIZE[Layers];
FreeAndSetBitArray(StuffedBits, StuffBits(Bits, WordSize));
end;
if StuffedBits = nil then
Continue;
UsableBitsInLayers := LocalTotalBitsInLayer -
(LocalTotalBitsInLayer mod WordSize);
if Compact and (StuffedBits.Size > WordSize * 64) then
// Compact format only allows 64 data words, though C4 can hold more words than that
Continue;
if StuffedBits.Size + EccBits <= UsableBitsInLayers then
Break;
end;
end;
MessageBits := GenerateCheckWords(StuffedBits, LocalTotalBitsInLayer,
WordSize);
// generate mode message
FCodeWords := StuffedBits.Size div WordSize;
ModeMessage := GenerateModeMessage;
// allocate symbol
BaseMatrixSize := IfValue(Compact, 11 + Layers * 4, 14 + Layers * 4);
// not including alignment lines
SetLength(AlignmentMap, BaseMatrixSize);
if Compact then
begin
// no alignment marks in compact mode, alignmentMap is a no-op
FMatrixSize := BaseMatrixSize;
for i := 0 to High(AlignmentMap) do
AlignmentMap[i] := i;
end
else
begin
FMatrixSize := BaseMatrixSize + 1 + 2 * ((BaseMatrixSize div 2 - 1) div 15);
OrigCenter := BaseMatrixSize div 2;
Center := MatrixSize div 2;
for i := 0 to OrigCenter - 1 do
begin
NewOffset := i + i div 15;
AlignmentMap[OrigCenter - i - 1] := Center - NewOffset - 1;
AlignmentMap[OrigCenter + i] := Center + NewOffset + 1
end;
end;
SetLength(FMatrix, MatrixSize, MatrixSize);
// draw data bits
RowOffset := 0;
for i := 0 to Layers - 1 do
begin
RowSize := IfValue(Compact, (Layers - i) * 4 + 9, (Layers - i) * 4 + 12);
for j := 0 to RowSize - 1 do
begin
ColumnOffset := j * 2;
for k := 0 to 2 - 1 do
begin
if MessageBits[RowOffset + ColumnOffset + k] then
SetBlack(AlignmentMap[i * 2 + k], AlignmentMap[i * 2 + j]);
if MessageBits[RowOffset + RowSize * 2 + ColumnOffset + k] then
SetBlack(AlignmentMap[i * 2 + j],
AlignmentMap[BaseMatrixSize - 1 - i * 2 - k]);
if MessageBits[RowOffset + RowSize * 4 + ColumnOffset + k] then
SetBlack(AlignmentMap[BaseMatrixSize - 1 - i * 2 - k],
AlignmentMap[BaseMatrixSize - 1 - i * 2 - j]);
if MessageBits[RowOffset + RowSize * 6 + ColumnOffset + k] then
SetBlack(AlignmentMap[BaseMatrixSize - 1 - i * 2 - j],
AlignmentMap[i * 2 + k]);
end
end;
RowOffset := RowOffset + RowSize * 8
end;
// draw mode message
DrawModeMessage(MatrixSize, ModeMessage);
// draw alignment marks
if Compact then
DrawBullsEye(MatrixSize div 2, 5)
else
begin
DrawBullsEye(MatrixSize div 2, 7);
i := 0;
j := 0;
while i < BaseMatrixSize div 2 - 1 do
begin
k := (MatrixSize div 2) and 1;
while k < MatrixSize do
begin
SetBlack(MatrixSize div 2 - j, k);
SetBlack(MatrixSize div 2 + j, k);
SetBlack(k, MatrixSize div 2 - j);
SetBlack(k, MatrixSize div 2 + j);
k := k + 2;
end;
i := i + 15;
j := j + 16;
end;
end;
Bits.Free;
StuffedBits.Free;
MessageBits.Free;
ModeMessage.Free;
HighLevelEncoder.Free;
FGarbage.Free;
end;
function TEncoder.GenerateCheckWords(BitArray: TBitArray;
TotalBits, WordSize: integer): TBitArray;
var
bufCodeWords, TotalWords, StartPad, i: integer;
RS: TReedSolomonEncoder;
MessageWords: TIntegerArray;
begin
if BitArray.Size mod WordSize <> 0 then
raise Exception.Create
('size of bit array is not a multiple of the word size');
// bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
bufCodeWords := BitArray.Size div WordSize;
RS := TReedSolomonEncoder.Create(GetGF(WordSize));
TotalWords := TotalBits div WordSize;
MessageWords := BitsToWords(BitArray, WordSize, TotalWords);
RS.Encode(MessageWords, TotalWords - bufCodeWords);
RS.Free;
StartPad := TotalBits mod WordSize;
Result := TBitArray.Create;
Result.AppendBits(0, StartPad);
for i := 0 to High(MessageWords) do
Result.AppendBits(MessageWords[i], WordSize);
end;
function TEncoder.GenerateModeMessage: TBitArray;
begin
Result := TBitArray.Create;
if Compact then
begin
Result.AppendBits(Layers - 1, 2);
Result.AppendBits(CodeWords - 1, 6);
FreeAndSetBitArray(Result, GenerateCheckWords(Result, 28, 4));
end
else
begin
Result.AppendBits(Layers - 1, 5);
Result.AppendBits(CodeWords - 1, 11);
FreeAndSetBitArray(Result, GenerateCheckWords(Result, 40, 4));
end;
end;
function TEncoder.GetGF(WordSize: integer): TGenericGF;
begin
case WordSize of
04:
Result := TGenericGF.CreateAztecParam;
06:
Result := TGenericGF.CreateAztecData6;
08:
Result := TGenericGF.CreateAztecData8;
10:
Result := TGenericGF.CreateAztecData10;
12:
Result := TGenericGF.CreateAztecData12;
else
Result := nil;
end;
if Assigned(Result) and Assigned(FGarbage) then
FGarbage.Add(Result);
end;
function TEncoder.GetMatrix(x, y: integer): Boolean;
begin
Result := FMatrix[x, y];
end;
procedure TEncoder.SetBlack(x, y: integer);
begin
FMatrix[x, y] := True;
end;
function TEncoder.StuffBits(Bits: TBitArray; WordSize: integer): TBitArray;
var
N, Mask, i, j: integer;
Word: LongInt;
begin
Result := TBitArray.Create;
N := Bits.Size;
Mask := (1 shl WordSize) - 2;
i := 0;
while i < N do
begin
Word := 0;
for j := 0 to WordSize - 1 do
if (i + j >= N) or Bits[i + j] then
Word := Word or (1 shl (WordSize - 1 - j));
if (Word and Mask) = Mask then
begin
Result.AppendBits(Word and Mask, WordSize);
Dec(i);
end
else if (Word and Mask) = 0 then
begin
Result.AppendBits(Word or 1, WordSize);
Dec(i);
end
else
Result.AppendBits(Word, WordSize);
Inc(i, WordSize);
end;
end;
function TEncoder.TotalBitsInLayer: integer;
begin
Result := (IfValue(Compact, 88, 112) + 16 * Layers) * Layers;
end;
{ initialization }
procedure CHAR_MAPInit;
const
mixedTable: array [0 .. 27] of AnsiChar = (#0, ' ', #1, #2, #3, #4, #5, #6,
#7, #8, #9, #10, #11, #12, #13, #27, #28, #29, #30, #31, '@', '\', '^', '_',
'`', '|', '~', #127);
punctTable: array [0 .. 30] of AnsiChar = (#0, #13, #0, #0, #0, #0, '!', '\',
'#', '$', '%', '&', '\', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';',
'<', '=', '>', '?', '[', ']', '{', '}');
var
c: AnsiChar;
i: integer;
begin
CHAR_MAP[MODE_UPPER][' '] := 1;
for c := 'A' to 'Z' do
CHAR_MAP[MODE_UPPER][c] := Ord(c) - Ord('A') + 2;
CHAR_MAP[MODE_LOWER][' '] := 1;
for c := 'a' to 'z' do
CHAR_MAP[MODE_LOWER][c] := Ord(c) - Ord('a') + 2;
CHAR_MAP[MODE_DIGIT][' '] := 1;
for c := '0' to '9' do
CHAR_MAP[MODE_DIGIT][c] := Ord(c) - Ord('0') + 2;
CHAR_MAP[MODE_DIGIT][','] := 12;
CHAR_MAP[MODE_DIGIT]['.'] := 13;
for i := 0 to High(mixedTable) do
CHAR_MAP[MODE_MIXED][mixedTable[i]] := i;
for i := 0 to High(punctTable) do
CHAR_MAP[MODE_PUNCT][punctTable[i]] := i;
end;
procedure SHIFT_TABLEInit;
var
i, j: integer;
begin
for i := 0 to ShiftTableSize - 1 do
for j := 0 to ShiftTableSize - 1 do
SHIFT_TABLE[i, j] := -1;
SHIFT_TABLE[MODE_UPPER][MODE_PUNCT] := 0;
SHIFT_TABLE[MODE_LOWER][MODE_PUNCT] := 0;
SHIFT_TABLE[MODE_LOWER][MODE_UPPER] := 28;
SHIFT_TABLE[MODE_MIXED][MODE_PUNCT] := 0;
SHIFT_TABLE[MODE_DIGIT][MODE_PUNCT] := 0;
SHIFT_TABLE[MODE_DIGIT][MODE_UPPER] := 15
end;
initialization
CHAR_MAPInit;
SHIFT_TABLEInit;
end.