use crate::{
common::{
reedsolomon::{
get_predefined_genericgf, GenericGFRef, PredefinedGenericGF, ReedSolomonEncoder,
},
BitArray, BitMatrix, CharacterSet, Result,
},
exceptions::Exceptions,
};
use super::{AztecCode, HighLevelEncoder};
pub const DEFAULT_EC_PERCENT: u32 = 33; pub const DEFAULT_AZTEC_LAYERS: i32 = 0;
pub const MAX_NB_BITS: u32 = 32;
pub const MAX_NB_BITS_COMPACT: u32 = 4;
pub const WORD_SIZE: [u32; 33] = [
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,
];
pub fn encode_simple(data: &str) -> Result<AztecCode> {
let Ok(bytes) = CharacterSet::ISO8859_1.encode_replace(data) else {
return Err(Exceptions::illegal_argument_with(format!(
"'{data}' cannot be encoded as ISO_8859_1"
)));
};
encode_bytes_simple(&bytes)
}
pub fn encode(data: &str, minECCPercent: u32, userSpecifiedLayers: i32) -> Result<AztecCode> {
if let Ok(bytes) = CharacterSet::ISO8859_1.encode(data) {
encode_bytes(&bytes, minECCPercent, userSpecifiedLayers)
} else {
Err(Exceptions::illegal_argument_with(format!(
"'{data}' cannot be encoded as ISO_8859_1"
)))
}
}
pub fn encode_with_charset(
data: &str,
minECCPercent: u32,
userSpecifiedLayers: i32,
charset: CharacterSet,
) -> Result<AztecCode> {
if let Ok(bytes) = charset.encode(data) {
encode_bytes_with_charset(&bytes, minECCPercent, userSpecifiedLayers, charset)
} else {
Err(Exceptions::illegal_argument_with(format!(
"'{data}' cannot be encoded as ISO_8859_1"
)))
}
}
pub fn encode_bytes_simple(data: &[u8]) -> Result<AztecCode> {
encode_bytes(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS)
}
pub fn encode_bytes(
data: &[u8],
minECCPercent: u32,
userSpecifiedLayers: i32,
) -> Result<AztecCode> {
encode_bytes_with_charset(
data,
minECCPercent,
userSpecifiedLayers,
CharacterSet::ISO8859_1,
)
}
pub fn encode_bytes_with_charset(
data: &[u8],
min_eccpercent: u32,
user_specified_layers: i32,
charset: CharacterSet,
) -> Result<AztecCode> {
let bits = HighLevelEncoder::with_charset(data.into(), charset).encode()?;
let ecc_bits = bits.get_size() as u32 * min_eccpercent / 100 + 11;
let total_size_bits = bits.get_size() as u32 + ecc_bits;
let mut compact;
let mut layers: u32;
let mut total_bits_in_layer_var;
let mut word_size;
let mut stuffed_bits;
if user_specified_layers != DEFAULT_AZTEC_LAYERS {
compact = user_specified_layers < 0;
layers = i32::abs(user_specified_layers) as u32;
if layers
> (if compact {
MAX_NB_BITS_COMPACT
} else {
MAX_NB_BITS
})
{
return Err(Exceptions::illegal_argument_with(format!(
"Illegal value {user_specified_layers} for layers"
)));
}
total_bits_in_layer_var = total_bits_in_layer(layers, compact);
word_size = WORD_SIZE[layers as usize];
let usable_bits_in_layers = total_bits_in_layer_var - (total_bits_in_layer_var % word_size);
stuffed_bits = stuffBits(&bits, word_size as usize)?;
if stuffed_bits.get_size() as u32 + ecc_bits > usable_bits_in_layers {
return Err(Exceptions::illegal_argument_with(
"Data to large for user specified layer",
));
}
if compact && stuffed_bits.get_size() as u32 > word_size * 64 {
return Err(Exceptions::illegal_argument_with(
"Data to large for user specified layer",
));
}
} else {
word_size = 0;
stuffed_bits = BitArray::new();
let mut i = 0;
loop {
if i > MAX_NB_BITS {
return Err(Exceptions::illegal_argument_with(
"Data too large for an Aztec code",
));
}
compact = i <= 3;
layers = if compact { i + 1 } else { i };
total_bits_in_layer_var = total_bits_in_layer(layers, compact);
if total_size_bits > total_bits_in_layer_var {
i += 1;
continue;
}
if stuffed_bits.get_size() == 0 || word_size != WORD_SIZE[layers as usize] {
word_size = WORD_SIZE[layers as usize];
stuffed_bits = stuffBits(&bits, word_size as usize)?;
}
let usable_bits_in_layers =
total_bits_in_layer_var - (total_bits_in_layer_var % word_size);
if compact && stuffed_bits.get_size() as u32 > word_size * 64 {
i += 1;
continue;
}
if stuffed_bits.get_size() as u32 + ecc_bits <= usable_bits_in_layers {
break;
}
i += 1;
}
}
let message_bits = generateCheckWords(
&stuffed_bits,
total_bits_in_layer_var as usize,
word_size as usize,
)?;
let messageSizeInWords = stuffed_bits.get_size() as u32 / word_size;
let modeMessage = generateModeMessage(compact, layers, messageSizeInWords)?;
let baseMatrixSize = (if compact { 11 } else { 14 }) + layers * 4; let mut alignmentMap = vec![0u32; baseMatrixSize as usize];
let matrixSize;
if compact {
matrixSize = baseMatrixSize;
alignmentMap[..].copy_from_slice(&(0..baseMatrixSize).collect::<Vec<u32>>()[..]);
} else {
matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
let origCenter = (baseMatrixSize / 2) as usize;
let center = matrixSize / 2;
for i in 0..origCenter {
let newOffset = (i + i / 15) as u32;
alignmentMap[origCenter - i - 1] = center - newOffset - 1;
alignmentMap[origCenter + i] = center + newOffset + 1;
}
}
let mut matrix = BitMatrix::with_single_dimension(matrixSize)?;
let mut rowOffset = 0;
for i in 0..layers as usize {
let rowSize = (layers as usize - i) * 4 + (if compact { 9 } else { 12 });
for j in 0..rowSize {
let columnOffset = j * 2;
for k in 0..2 {
if message_bits.get(rowOffset + columnOffset + k) {
matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
}
if message_bits.get(rowOffset + rowSize * 2 + columnOffset + k) {
matrix.set(
alignmentMap[i * 2 + j],
alignmentMap[baseMatrixSize as usize - 1 - i * 2 - k],
);
}
if message_bits.get(rowOffset + rowSize * 4 + columnOffset + k) {
matrix.set(
alignmentMap[baseMatrixSize as usize - 1 - i * 2 - k],
alignmentMap[baseMatrixSize as usize - 1 - i * 2 - j],
);
}
if message_bits.get(rowOffset + rowSize * 6 + columnOffset + k) {
matrix.set(
alignmentMap[baseMatrixSize as usize - 1 - i * 2 - j],
alignmentMap[i * 2 + k],
);
}
}
}
rowOffset += rowSize * 8;
}
drawModeMessage(&mut matrix, compact, matrixSize, modeMessage);
if compact {
drawBullsEye(&mut matrix, matrixSize / 2, 5);
} else {
drawBullsEye(&mut matrix, matrixSize / 2, 7);
let mut i = 0;
let mut j = 0;
while i < baseMatrixSize / 2 - 1 {
let mut k = (matrixSize / 2) & 1;
while k < matrixSize {
matrix.set(matrixSize / 2 - j, k);
matrix.set(matrixSize / 2 + j, k);
matrix.set(k, matrixSize / 2 - j);
matrix.set(k, matrixSize / 2 + j);
k += 2;
}
i += 15;
j += 16;
}
}
let aztec = AztecCode::new(compact, matrixSize, layers, messageSizeInWords, matrix);
Ok(aztec)
}
fn drawBullsEye(matrix: &mut BitMatrix, center: u32, size: u32) {
let mut i = 0;
while i < size {
for j in (center - i)..=(center + i) {
matrix.set(j, center - i);
matrix.set(j, center + i);
matrix.set(center - i, j);
matrix.set(center + i, j);
}
i += 2;
}
matrix.set(center - size, center - size);
matrix.set(center - size + 1, center - size);
matrix.set(center - size, center - size + 1);
matrix.set(center + size, center - size);
matrix.set(center + size, center - size + 1);
matrix.set(center + size, center + size - 1);
}
pub fn generateModeMessage(
compact: bool,
layers: u32,
messageSizeInWords: u32,
) -> Result<BitArray> {
let mut mode_message = BitArray::new();
if compact {
mode_message.appendBits(layers - 1, 2)?;
mode_message.appendBits(messageSizeInWords - 1, 6)?;
mode_message = generateCheckWords(&mode_message, 28, 4)?;
} else {
mode_message.appendBits(layers - 1, 5)?;
mode_message.appendBits(messageSizeInWords - 1, 11)?;
mode_message = generateCheckWords(&mode_message, 40, 4)?;
}
Ok(mode_message)
}
fn drawModeMessage(matrix: &mut BitMatrix, compact: bool, matrixSize: u32, modeMessage: BitArray) {
let center = matrixSize / 2;
if compact {
for i in 0..7_usize {
let offset = (center as usize - 3 + i) as u32;
if modeMessage.get(i) {
matrix.set(offset, center - 5);
}
if modeMessage.get(i + 7) {
matrix.set(center + 5, offset);
}
if modeMessage.get(20 - i) {
matrix.set(offset, center + 5);
}
if modeMessage.get(27 - i) {
matrix.set(center - 5, offset);
}
}
} else {
for i in 0..10_usize {
let offset = (center as usize - 5 + i + i / 5) as u32;
if modeMessage.get(i) {
matrix.set(offset, center - 7);
}
if modeMessage.get(i + 10) {
matrix.set(center + 7, offset);
}
if modeMessage.get(29 - i) {
matrix.set(offset, center + 7);
}
if modeMessage.get(39 - i) {
matrix.set(center - 7, offset);
}
}
}
}
fn generateCheckWords(bitArray: &BitArray, totalBits: usize, wordSize: usize) -> Result<BitArray> {
let message_size_in_words = bitArray.get_size() / wordSize;
let mut rs = ReedSolomonEncoder::new(getGF(wordSize)?)?;
let total_words = totalBits / wordSize;
let mut message_words = bitsToWords(bitArray, wordSize, total_words);
rs.encode(&mut message_words, total_words - message_size_in_words)?;
let start_pad = totalBits % wordSize;
let mut message_bits = BitArray::new();
message_bits.appendBits(0, start_pad)?;
for message_word in message_words {
message_bits.appendBits(message_word as u32, wordSize)?
}
Ok(message_bits)
}
fn bitsToWords(stuffedBits: &BitArray, wordSize: usize, totalWords: usize) -> Vec<i32> {
let mut message = vec![0; totalWords];
let mut i = 0;
let n = stuffedBits.get_size() / wordSize;
while i < n {
let mut value = 0;
for j in 0..wordSize {
value |= if stuffedBits.get(i * wordSize + j) {
1 << (wordSize - j - 1)
} else {
0
};
}
message[i] = value;
i += 1;
}
message
}
fn getGF(wordSize: usize) -> Result<GenericGFRef> {
match wordSize {
4 => Ok(get_predefined_genericgf(PredefinedGenericGF::AztecParam)),
6 => Ok(get_predefined_genericgf(PredefinedGenericGF::AztecData6)),
8 => Ok(get_predefined_genericgf(PredefinedGenericGF::AztecData8)),
10 => Ok(get_predefined_genericgf(PredefinedGenericGF::AztecData10)),
12 => Ok(get_predefined_genericgf(PredefinedGenericGF::AztecData12)),
_ => Err(Exceptions::illegal_argument_with(format!(
"Unsupported word size {wordSize}"
))),
}
}
pub fn stuffBits(bits: &BitArray, word_size: usize) -> Result<BitArray> {
let mut out = BitArray::new();
let n = bits.get_size() as isize;
let mask = (1 << word_size) - 2;
let mut i: isize = 0;
while i < n {
let mut word = 0;
for j in 0..word_size as isize {
if i + j >= n || bits.get((i + j) as usize) {
word |= 1 << (word_size as isize - 1 - j);
}
}
if (word & mask) == mask {
out.appendBits(word & mask, word_size)?;
i -= 1;
} else if (word & mask) == 0 {
out.appendBits(word | 1, word_size)?;
i -= 1;
} else {
out.appendBits(word, word_size)?;
}
i += word_size as isize;
}
Ok(out)
}
fn total_bits_in_layer(layers: u32, compact: bool) -> u32 {
((if compact { 88 } else { 112 }) + 16 * layers) * layers
}