non passing aztec decoder port

This commit is contained in:
Henry Schimke
2022-09-19 21:48:40 -05:00
parent 3cb5973be2
commit 1b3535c0df
13 changed files with 1131 additions and 894 deletions

View File

@@ -1,58 +0,0 @@
/*
* 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.
*/
package com.google.zxing.aztec;
import com.google.zxing.RXingResultPoint;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DetectorRXingResult;
/**
* <p>Extends {@link DetectorRXingResult} with more information specific to the Aztec format,
* like the number of layers and whether it's compact.</p>
*
* @author Sean Owen
*/
public final class AztecDetectorRXingResult extends DetectorRXingResult {
private final boolean compact;
private final int nbDatablocks;
private final int nbLayers;
public AztecDetectorRXingResult(BitMatrix bits,
RXingResultPoint[] points,
boolean compact,
int nbDatablocks,
int nbLayers) {
super(bits, points);
this.compact = compact;
this.nbDatablocks = nbDatablocks;
this.nbLayers = nbLayers;
}
public int getNbLayers() {
return nbLayers;
}
public int getNbDatablocks() {
return nbDatablocks;
}
public boolean isCompact() {
return compact;
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.
*/
// package com.google.zxing.aztec;
// import com.google.zxing.RXingResultPoint;
// import com.google.zxing.common.BitMatrix;
// import com.google.zxing.common.DetectorRXingResult;
use crate::{
common::{BitMatrix, DetectorRXingResult},
RXingResultPoint,
};
/**
* <p>Extends {@link DetectorRXingResult} with more information specific to the Aztec format,
* like the number of layers and whether it's compact.</p>
*
* @author Sean Owen
*/
pub struct AztecDetectorRXingResult {
bits: BitMatrix,
points: Vec<RXingResultPoint>,
compact: bool,
nbDatablocks: u32,
nbLayers: u32,
}
impl DetectorRXingResult for AztecDetectorRXingResult {
fn getBits(&self) -> &BitMatrix {
&self.bits
}
fn getPoints(&self) -> &Vec<RXingResultPoint> {
&self.points
}
}
impl AztecDetectorRXingResult {
pub fn new(
bits: BitMatrix,
points: Vec<RXingResultPoint>,
compact: bool,
nbDatablocks: u32,
nbLayers: u32,
) -> Self {
Self {
bits,
points,
compact,
nbDatablocks,
nbLayers,
}
}
pub fn getNbLayers(&self) -> u32 {
self.nbLayers
}
pub fn getNbDatablocks(&self) -> u32 {
self.nbDatablocks
}
pub fn isCompact(&self) -> bool {
self.compact
}
}

285
src/aztec/DecoderTest.rs Normal file
View File

@@ -0,0 +1,285 @@
/*
* Copyright 2014 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.
*/
// package com.google.zxing.aztec.decoder;
// import com.google.zxing.aztec.encoder.EncoderTest;
// import com.google.zxing.FormatException;
// import com.google.zxing.RXingResultPoint;
// import com.google.zxing.aztec.AztecDetectorRXingResult;
// import com.google.zxing.common.BitArray;
// import com.google.zxing.common.BitMatrix;
// import com.google.zxing.common.DecoderRXingResult;
// import org.junit.Test;
// import org.junit.Assert;
use crate::{
aztec::shared_test_methods::{stripSpace, toBitArray, toBooleanArray},
common::BitMatrix,
RXingResultPoint,
};
use super::{decoder, AztecDetectorResult::AztecDetectorRXingResult};
/**
* Tests {@link Decoder}.
*/
const NO_POINTS: &[RXingResultPoint] = &[RXingResultPoint { x: 0.0, y: 0.0 }; 0];
#[test]
fn testHighLevelDecode() {
// no ECI codes
testHighLevelDecodeString(
"A. b.",
// 'A' P/S '. ' L/L b D/L '.'
"...X. ..... ...XX XXX.. ...XX XXXX. XX.X",
);
// initial ECI code 26 (switch to UTF-8)
testHighLevelDecodeString(
"Ça",
// P/S FLG(n) 2 '2' '6' B/S 2 0xc3 0x87 L/L 'a'
"..... ..... .X. .X.. X... XXXXX ...X. XX....XX X....XXX XXX.. ...X.",
);
// initial character without ECI (must be interpreted as ISO_8859_1)
// followed by ECI code 26 (= UTF-8) and UTF-8 text
testHighLevelDecodeString(
"±Ça",
// B/S 1 0xb1 P/S FLG(n) 2 '2' '6' B/S 2 0xc3 0x87 L/L 'a'
"XXXXX ....X X.XX...X ..... ..... .X. .X.. X... XXXXX ...X. XX....XX X....XXX XXX.. ...X.",
);
// GS1 data
testHighLevelDecodeString(
"101233742",
// P/S FLG(n) 0 D/L 1 0 1 2 3 P/S FLG(n) 0 3 7 4 2
"..... ..... ... XXXX. ..XX ..X. ..XX .X.. .X.X .... ..... ... .X.X X..X .XX. .X..",
);
}
fn testHighLevelDecodeString(expectedString: &str, b: &str) {
let bits = toBitArray(&stripSpace(b));
assert_eq!(
expectedString,
decoder::highLevelDecode(&toBooleanArray(&bits)).expect("highLevelDecode Failed"),
"highLevelDecode() failed for input bits: {}",
b
);
}
#[test]
fn testAztecRXingResult() {
let matrix = BitMatrix::parse_strings(
r"X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X
X X X X X X X X X X X X
X X X X X X X X X X
X X X X X X X X
X X X X X X X X X X X X X X X X X X
X X X X X X X X X
X X X X X X X X X X X X X X X X X
X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X
X X X X X X X X X X X
X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X
X X X X X X X X X X X
X X X X X X X X X X X X X X
X X X X X X X X
X X X X X X X X X X X X X X X X X X X
X X X X X X X X X
X X X X X X X X X X X X X X X
X X X X X X X X X X X X
X X X X X X X X X X X X X X
X X X X X X X X X X X X X
",
"X ",
" ",
)
.expect("Bitmatrix should init");
let r = AztecDetectorRXingResult::new(matrix, NO_POINTS.to_vec(), false, 30, 2);
let result = decoder::decode(&r).expect("decoder should init");
assert_eq!("88888TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", result.getText());
assert_eq!(
&vec![
-11i8 as u8,
85,
85,
117,
107,
90,
-42i8 as u8,
-75i8 as u8,
-83i8 as u8,
107,
90,
-42i8 as u8,
-75i8 as u8,
-83i8 as u8,
107,
90,
-42i8 as u8,
-75i8 as u8,
-83i8 as u8,
107,
90,
-42i8 as u8,
-80i8 as u8
],
result.getRawBytes()
);
assert_eq!(180, result.getNumBits());
}
#[test]
fn testAztecRXingResultECI() {
let matrix = BitMatrix::parse_strings(
r" X X X X X X
X X X X X X X X X X X X
X X X X
X X X X X X X X X X X X X X X X X
X X
X X X X X X X X X X X X
X X X X X X X X
X X X X X X X X X X X X
X X X X X X X X
X X X X X X X X X
X X X X X X X X X
X X X X X X X X X X X X
X X X X X X
X X X X X X X X X X X X X
X X X
X X X X X X X X X X X X X X X X X
X X X X X X X X X
X X X X X X X X X X X
X X X X X X X X ",
"X ",
" ",
)
.expect("string parse success");
let r = AztecDetectorRXingResult::new(matrix, NO_POINTS.to_vec(), false, 15, 1);
let result = decoder::decode(&r).expect("decode success");
assert_eq!("Français", result.getText());
}
#[test]
#[should_panic]
fn testDecodeTooManyErrors() {
let matrix = BitMatrix::parse_strings(
r"
X X . X . . . X X . . . X . . X X X . X . X X X X X .
X X . . X X . . . . . X X . . . X X . . . X . X . . X
X . . . X X . . X X X . X X . X X X X . X X . . X . .
. . . . X . X X . . X X . X X . X . X X X X . X . . X
X X X . . X X X X X . . . . . X X . . . X . X . X . X
X X . . . . . . . . X . . . X . X X X . X . . X . . .
X X . . X . . . . . X X . . . . . X . . . . X . . X X \
. . . X . X . X . . . . . X X X X X X . . . . . . X X
X . . . X . X X X X X X . . X X X . X . X X X X X X .
X . . X X X . X X X X X X X X X X X X X . . . X . X X
. . . . X X . . . X . . . . . . . X X . . . X X . X .
. . . X X X . . X X . X X X X X . X . . X . . . . . .
X . . . . X . X . X . X . . . X . X . X X . X X . X X
X . X . . X . X . X . X . X . X . X . . . . . X . X X
X . X X X . . X . X . X . . . X . X . X X X . . . X X
X X X X X X X X . X . X X X X X . X . X . X . X X X .
. . . . . . . X . X . . . . . . . X X X X . . . X X X
X X . . X . . X . X X X X X X X X X X X X X . . X . X
X X X . X X X X . . X X X X . . X . . . . X . . X X X
. . . . X . X X X . . . . X X X X . . X X X X . . . .
. . X . . X . X . . . X . X X . X X . X . . . X . X .
X X . . X . . X X X X X X X . . X . X X X X X X X . .
X . X X . . X X . . . . . X . . . . . . X X . X X X .
X . . X X . . X X . X . X . . . . X . X . . X . . X .
X . X . X . . X . X X X X X X X X . X X X X . . X X .
X X X X . . . X . . X X X . X X . . X . . . . X X X .
X X . X . X . . . X . X . . . . X X . X . . X X . . . ",
"X ",
". ",
)
.expect("parse string success");
let r = AztecDetectorRXingResult::new(matrix, NO_POINTS.to_vec(), true, 16, 4);
decoder::decode(&r);
}
#[test]
#[should_panic]
fn testDecodeTooManyErrors2() {
let matrix = BitMatrix::parse_strings(
r"
. X X . . X . X X . . . X . . X X X . . . X X . X X .
X X . X X . . X . . . X X . . . X X . X X X . X . X X
. . . . X . . . X X X . X X . X X X X . X X . . X . .
X . X X . . X . . . X X . X X . X . X X . . . . . X .
X X . X . . X . X X . . . . . X X . . . . . X . . . X
X . . X . . . . . . X . . . X . X X X X X X X . . . X
X . . X X . . X . . X X . . . . . X . . . . . X X X .
. . X X X X . X . . . . . X X X X X X . . . . . . X X
X . . . X . X X X X X X . . X X X . X . X X X X X X .
X . . X X X . X X X X X X X X X X X X X . . . X . X X
. . . . X X . . . X . . . . . . . X X . . . X X . X .
. . . X X X . . X X . X X X X X . X . . X . . . . . .
X . . . . X . X . X . X . . . X . X . X X . X X . X X
X . X . . X . X . X . X . X . X . X . . . . . X . X X
X . X X X . . X . X . X . . . X . X . X X X . . . X X
X X X X X X X X . X . X X X X X . X . X . X . X X X .
. . . . . . . X . X . . . . . . . X X X X . . . X X X
X X . . X . . X . X X X X X X X X X X X X X . . X . X
X X X . X X X X . . X X X X . . X . . . . X . . X X X
. . X X X X X . X . . . . X X X X . . X X X . X . X .
. . X X . X . X . . . X . X X . X X . . . . X X . . .
X . . . X . X . X X X X X X . . X . X X X X X . X . .
. X . . . X X X . . . . . X . . . . . X X X X X . X .
X . . X . X X X X . X . X . . . . X . X X . X . . X .
X . . . X X . X . X X X X X X X X . X X X X . . X X .
. X X X X . . X . . X X X . X X . . X . . . . X X X .
X X . . . X X . . X . X . . . . X X . X . . X . X . X ",
"X ",
". ",
)
.expect("String Parse OK");
let r = AztecDetectorRXingResult::new(matrix, NO_POINTS.to_vec(), true, 16, 4);
decoder::decode(&r);
}
#[test]
fn testRawBytes() {
let bool0 = vec![false; 0];
let bool1 = vec![true];
let bool7 = vec![true, false, true, false, true, false, true];
let bool8 = vec![true, false, true, false, true, false, true, false];
let bool9 = vec![true, false, true, false, true, false, true, false, true];
let bool16 = vec![
false, true, true, false, false, false, true, true, true, true, false, false, false, false,
false, true,
];
let byte0 = vec![0u8; 0];
let byte1 = vec![-128i8 as u8];
let byte7 = vec![-86i8 as u8];
let byte8 = vec![-86i8 as u8];
let byte9 = vec![-86i8 as u8, -128i8 as u8];
let byte16 = vec![99, -63i8 as u8];
assert_eq!(byte0, decoder::convertBoolArrayToByteArray(&bool0));
assert_eq!(byte1, decoder::convertBoolArrayToByteArray(&bool1));
assert_eq!(byte7, decoder::convertBoolArrayToByteArray(&bool7));
assert_eq!(byte8, decoder::convertBoolArrayToByteArray(&bool8));
assert_eq!(byte9, decoder::convertBoolArrayToByteArray(&bool9));
assert_eq!(byte16, decoder::convertBoolArrayToByteArray(&bool16));
}

189
src/aztec/DetectorTest.java Normal file
View File

@@ -0,0 +1,189 @@
/*
* 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/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.
*/
package com.google.zxing.aztec.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.aztec.AztecDetectorRXingResult;
import com.google.zxing.aztec.decoder.Decoder;
import com.google.zxing.aztec.detector.Detector.Point;
import com.google.zxing.aztec.encoder.AztecCode;
import com.google.zxing.aztec.encoder.Encoder;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderRXingResult;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;
/**
* Tests for the Detector
*
* @author Frank Yellin
*/
public final class DetectorTest extends Assert {
@Test
public void testErrorInParameterLocatorZeroZero() throws Exception {
// Layers=1, CodeWords=1. So the parameter info and its Reed-Solomon info
// will be completely zero!
testErrorInParameterLocator("X");
}
@Test
public void testErrorInParameterLocatorCompact() throws Exception {
testErrorInParameterLocator("This is an example Aztec symbol for Wikipedia.");
}
@Test
public void testErrorInParameterLocatorNotCompact() throws Exception {
String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz";
testErrorInParameterLocator(alphabet + alphabet + alphabet);
}
// Test that we can tolerate errors in the parameter locator bits
private static void testErrorInParameterLocator(String data) throws Exception {
AztecCode aztec = Encoder.encode(data, 25, Encoder.DEFAULT_AZTEC_LAYERS);
Random random = new Random(aztec.getMatrix().hashCode()); // pseudo-random, but deterministic
int layers = aztec.getLayers();
boolean compact = aztec.isCompact();
List<Point> orientationPoints = getOrientationPoints(aztec);
for (boolean isMirror : new boolean[] { false, true }) {
for (BitMatrix matrix : getRotations(aztec.getMatrix())) {
// Systematically try every possible 1- and 2-bit error.
for (int error1 = 0; error1 < orientationPoints.size(); error1++) {
for (int error2 = error1; error2 < orientationPoints.size(); error2++) {
BitMatrix copy = isMirror ? transpose(matrix) : clone(matrix);
copy.flip(orientationPoints.get(error1).getX(), orientationPoints.get(error1).getY());
if (error2 > error1) {
// if error2 == error1, we only test a single error
copy.flip(orientationPoints.get(error2).getX(), orientationPoints.get(error2).getY());
}
// The detector doesn't seem to work when matrix bits are only 1x1. So magnify.
AztecDetectorRXingResult r = new Detector(makeLarger(copy, 3)).detect(isMirror);
assertNotNull(r);
assertEquals(r.getNbLayers(), layers);
assertEquals(r.isCompact(), compact);
DecoderRXingResult res = new Decoder().decode(r);
assertEquals(data, res.getText());
}
}
// Try a few random three-bit errors;
for (int i = 0; i < 5; i++) {
BitMatrix copy = clone(matrix);
Collection<Integer> errors = new TreeSet<>();
while (errors.size() < 3) {
// Quick and dirty way of getting three distinct integers between 1 and n.
errors.add(random.nextInt(orientationPoints.size()));
}
for (int error : errors) {
copy.flip(orientationPoints.get(error).getX(), orientationPoints.get(error).getY());
}
try {
new Detector(makeLarger(copy, 3)).detect(false);
fail("Should not reach here");
} catch (NotFoundException expected) {
// continue
}
}
}
}
}
// Zooms a bit matrix so that each bit is factor x factor
private static BitMatrix makeLarger(BitMatrix input, int factor) {
int width = input.getWidth();
BitMatrix output = new BitMatrix(width * factor);
for (int inputY = 0; inputY < width; inputY++) {
for (int inputX = 0; inputX < width; inputX++) {
if (input.get(inputX, inputY)) {
output.setRegion(inputX * factor, inputY * factor, factor, factor);
}
}
}
return output;
}
// Returns a list of the four rotations of the BitMatrix.
private static Iterable<BitMatrix> getRotations(BitMatrix matrix0) {
BitMatrix matrix90 = rotateRight(matrix0);
BitMatrix matrix180 = rotateRight(matrix90);
BitMatrix matrix270 = rotateRight(matrix180);
return Arrays.asList(matrix0, matrix90, matrix180, matrix270);
}
// Rotates a square BitMatrix to the right by 90 degrees
private static BitMatrix rotateRight(BitMatrix input) {
int width = input.getWidth();
BitMatrix result = new BitMatrix(width);
for (int x = 0; x < width; x++) {
for (int y = 0; y < width; y++) {
if (input.get(x,y)) {
result.set(y, width - x - 1);
}
}
}
return result;
}
// Returns the transpose of a bit matrix, which is equivalent to rotating the
// matrix to the right, and then flipping it left-to-right
private static BitMatrix transpose(BitMatrix input) {
int width = input.getWidth();
BitMatrix result = new BitMatrix(width);
for (int x = 0; x < width; x++) {
for (int y = 0; y < width; y++) {
if (input.get(x, y)) {
result.set(y, x);
}
}
}
return result;
}
private static BitMatrix clone(BitMatrix input) {
int width = input.getWidth();
BitMatrix result = new BitMatrix(width);
for (int x = 0; x < width; x++) {
for (int y = 0; y < width; y++) {
if (input.get(x,y)) {
result.set(x,y);
}
}
}
return result;
}
private static List<Point> getOrientationPoints(AztecCode code) {
int center = code.getMatrix().getWidth() / 2;
int offset = code.isCompact() ? 5 : 7;
List<Point> result = new ArrayList<>();
for (int xSign = -1; xSign <= 1; xSign += 2) {
for (int ySign = -1; ySign <= 1; ySign += 2) {
result.add(new Point(center + xSign * offset, center + ySign * offset));
result.add(new Point(center + xSign * (offset - 1), center + ySign * offset));
result.add(new Point(center + xSign * offset, center + ySign * (offset - 1)));
}
}
return result;
}
}

586
src/aztec/EncoderTest.rs Normal file
View File

@@ -0,0 +1,586 @@
/*
* 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/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.
*/
// package com.google.zxing.aztec.encoder;
// import com.google.zxing.BarcodeFormat;
// import com.google.zxing.EncodeHintType;
// import com.google.zxing.FormatException;
// import com.google.zxing.RXingResultPoint;
// import com.google.zxing.aztec.AztecDetectorRXingResult;
// import com.google.zxing.aztec.AztecWriter;
// import com.google.zxing.aztec.decoder.Decoder;
// import com.google.zxing.common.BitArray;
// import com.google.zxing.common.BitMatrix;
// import com.google.zxing.common.DecoderRXingResult;
// import org.junit.Assert;
// import org.junit.Test;
// import java.nio.charset.Charset;
// import java.nio.charset.StandardCharsets;
// import java.util.EnumMap;
// import java.util.Map;
// import java.util.Random;
// import java.util.regex.Pattern;
use crate::common::BitArray;
/**
* Aztec 2D generator unit tests.
*
* @author Rustam Abdullaev
* @author Frank Yellin
*/
const ISO_8859_1:&'static encoding::Encoding = StandardCharsets.ISO_8859_1;
const UTF_8 :&'static encoding::Encoding= StandardCharsets.UTF_8;
const SHIFT_JIS:&'static encoding::Encoding = Charset.forName("Shift_JIS");
const ISO_8859_15:&'static encoding::Encoding = Charset.forName("ISO-8859-15");
const WINDOWS_1252 :&'static encoding::Encoding= Charset.forName("Windows-1252");
const DOTX : &str = "[^.X]";
const SPACES :&str= "\\s+";
const NO_POINTS:Vec<RXingResultPoint> = Vec::new();
// real life tests
#[test]
fn testEncode1() {
testEncode("This is an example Aztec symbol for Wikipedia.", true, 3,
"X X X X X X X X \n" +
"X X X X X X X X X X \n" +
"X X X X X X X X X X X \n" +
"X X X X X X X X X X X \n" +
" X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X \n" +
"X X X X X X X X X X \n" +
" X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X \n" +
" X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X \n" +
" X X X \n" +
" X X X X X X X X X X \n" +
" X X X X X X X X X X \n");
}
#[test]
fn testEncode2() {
testEncode("Aztec Code is a public domain 2D matrix barcode symbology" +
" of nominally square symbols built on a square grid with a " +
"distinctive square bullseye pattern at their center.", false, 6,
" X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X X X X X X X X X \n" +
" X X X X X X X X X X X X X X X X \n" +
"X X X X X X X X X X X X X \n");
}
fn testAztecWriter() {
testWriter("Espa\u{00F1}ol", null, 25, true, 1); // Without ECI (implicit ISO-8859-1)
testWriter("Espa\u{00F1}ol", ISO_8859_1, 25, true, 1); // Explicit ISO-8859-1
testWriter("\u{20AC} 1 sample data.", WINDOWS_1252, 25, true, 2); // ISO-8859-1 can't encode Euro; Windows-1252 can
testWriter("\u{20AC} 1 sample data.", ISO_8859_15, 25, true, 2);
testWriter("\u{20AC} 1 sample data.", UTF_8, 25, true, 2);
testWriter("\u{20AC} 1 sample data.", UTF_8, 100, true, 3);
testWriter("\u{20AC} 1 sample data.", UTF_8, 300, true, 4);
testWriter("\u{20AC} 1 sample data.", UTF_8, 500, false, 5);
testWriter("The capital of Japan is named \u{6771}\u{4EAC}.", SHIFT_JIS, 25, true, 3);
// Test AztecWriter defaults
let data = "In ut magna vel mauris malesuada";
let writer = AztecWriter::new();
let matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0);
let aztec = Encoder.encode(data,
Encoder.DEFAULT_EC_PERCENT, Encoder.DEFAULT_AZTEC_LAYERS);
let expectedMatrix = aztec.getMatrix();
assertEquals(matrix, expectedMatrix);
}
// synthetic tests (encode-decode round-trip)
#[test]
fn testEncodeDecode1() {
testEncodeDecode("Abc123!", true, 1);
}
#[test]
fn testEncodeDecode2() {
testEncodeDecode("Lorem ipsum. http://test/", true, 2);
}
#[test]
fn testEncodeDecode3() {
testEncodeDecode("AAAANAAAANAAAANAAAANAAAANAAAANAAAANAAAANAAAANAAAAN", true, 3);
}
#[test]
fn testEncodeDecode4() {
testEncodeDecode("http://test/~!@#*^%&)__ ;:'\"[]{}\\|-+-=`1029384", true, 4);
}
#[test]
fn testEncodeDecode5() {
testEncodeDecode("http://test/~!@#*^%&)__ ;:'\"[]{}\\|-+-=`1029384756<>/?abc"
+ "Four score and seven our forefathers brought forth", false, 5);
}
#[test]
fn testEncodeDecode10() {
testEncodeDecode("In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam" +
" cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum" +
" est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue" +
" auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla" +
" ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id" +
" elementum sapien dolor et diam.", false, 10);
}
#[test]
fn testEncodeDecode23() {
testEncodeDecode("In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam" +
" cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum" +
" est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue" +
" auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla" +
" ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id" +
" elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend." +
" Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus" +
" justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu" +
" tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus" +
" quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec" +
" laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida," +
" justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec" +
" lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar" +
" nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat" +
" eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra" +
" fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo" +
" diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla" +
" ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum" +
" sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet." +
" Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit" +
" felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo" +
" erat pulvinar nisi, id elementum sapien dolor et diam.", false, 23);
}
#[test]
fn testEncodeDecode31() {
testEncodeDecode("In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam" +
" cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum" +
" est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue" +
" auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla" +
" ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id" +
" elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend." +
" Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus" +
" justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu" +
" tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus" +
" quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec" +
" laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida," +
" justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec" +
" lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar" +
" nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat" +
" eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra" +
" fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo" +
" diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla" +
" ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum" +
" sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet." +
" Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit" +
" felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo" +
" erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit" +
" placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at" +
" pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est." +
" Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada" +
" dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id" +
" justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum" +
" sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat," +
" eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet" +
" laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac" +
" nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula," +
" massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus" +
" sed est. Ut justo diam, lobortis eu tris. In ut magna vel mauris malesuada dictum." +
" Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum" +
" sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet." +
" Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget" +
" hendrerit felis turpis nec lorem.", false, 31);
}
#[test]
fn testGenerateModeMessage() {
testModeMessage(true, 2, 29, ".X .XXX.. ...X XX.. ..X .XX. .XX.X");
testModeMessage(true, 4, 64, "XX XXXXXX .X.. ...X ..XX .X.. XX..");
testModeMessage(false, 21, 660, "X.X.. .X.X..X..XX .XXX ..X.. .XXX. .X... ..XXX");
testModeMessage(false, 32, 4096, "XXXXX XXXXXXXXXXX X.X. ..... XXX.X ..X.. X.XXX");
}
#[test]
fn testStuffBits() {
testStuffBits(5, ".X.X. X.X.X .X.X.",
".X.X. X.X.X .X.X.");
testStuffBits(5, ".X.X. ..... .X.X",
".X.X. ....X ..X.X");
testStuffBits(3, "XX. ... ... ..X XXX .X. ..",
"XX. ..X ..X ..X ..X .XX XX. .X. ..X");
testStuffBits(6, ".X.X.. ...... ..X.XX",
".X.X.. .....X. ..X.XX XXXX.");
testStuffBits(6, ".X.X.. ...... ...... ..X.X.",
".X.X.. .....X .....X ....X. X.XXXX");
testStuffBits(6, ".X.X.. XXXXXX ...... ..X.XX",
".X.X.. XXXXX. X..... ...X.X XXXXX.");
testStuffBits(6,
"...... ..XXXX X..XX. .X.... .X.X.X .....X .X.... ...X.X .....X ....XX ..X... ....X. X..XXX X.XX.X",
".....X ...XXX XX..XX ..X... ..X.X. X..... X.X... ....X. X..... X....X X..X.. .....X X.X..X XXX.XX .XXXXX");
}
#[test]
fn testHighLevelEncode() {
testHighLevelEncodeString("A. b.",
// 'A' P/S '. ' L/L b D/L '.'
"...X. ..... ...XX XXX.. ...XX XXXX. XX.X");
testHighLevelEncodeString("Lorem ipsum.",
// 'L' L/L 'o' 'r' 'e' 'm' ' ' 'i' 'p' 's' 'u' 'm' D/L '.'
".XX.X XXX.. X.... X..XX ..XX. .XXX. ....X .X.X. X...X X.X.. X.XX. .XXX. XXXX. XX.X");
testHighLevelEncodeString("Lo. Test 123.",
// 'L' L/L 'o' P/S '. ' U/S 'T' 'e' 's' 't' D/L ' ' '1' '2' '3' '.'
".XX.X XXX.. X.... ..... ...XX XXX.. X.X.X ..XX. X.X.. X.X.X XXXX. ...X ..XX .X.. .X.X XX.X");
testHighLevelEncodeString("Lo...x",
// 'L' L/L 'o' D/L '.' '.' '.' U/L L/L 'x'
".XX.X XXX.. X.... XXXX. XX.X XX.X XX.X XXX. XXX.. XX..X");
testHighLevelEncodeString(". x://abc/.",
//P/S '. ' L/L 'x' P/S ':' P/S '/' P/S '/' 'a' 'b' 'c' P/S '/' D/L '.'
"..... ...XX XXX.. XX..X ..... X.X.X ..... X.X.. ..... X.X.. ...X. ...XX ..X.. ..... X.X.. XXXX. XX.X");
// Uses Binary/Shift rather than Lower/Shift to save two bits.
testHighLevelEncodeString("ABCdEFG",
//'A' 'B' 'C' B/S =1 'd' 'E' 'F' 'G'
"...X. ...XX ..X.. XXXXX ....X .XX..X.. ..XX. ..XXX .X...");
testHighLevelEncodeString(
// Found on an airline boarding pass. Several stretches of Binary shift are
// necessary to keep the bitcount so low.
"09 UAG ^160MEUCIQC0sYS/HpKxnBELR1uB85R20OoqqwFGa0q2uEi"
+ "Ygh6utAIgLl1aBVM4EOTQtMQQYH9M2Z3Dp4qnA/fwWuQ+M8L3V8U=",
823);
}
#[test]
fn testHighLevelEncodeBinary() {
// binary short form single byte
testHighLevelEncodeString("N\0N",
// 'N' B/S =1 '\0' N
".XXXX XXXXX ....X ........ .XXXX"); // Encode "N" in UPPER
testHighLevelEncodeString("N\0n",
// 'N' B/S =2 '\0' 'n'
".XXXX XXXXX ...X. ........ .XX.XXX."); // Encode "n" in BINARY
// binary short form consecutive bytes
testHighLevelEncodeString("N\0\u{0080} A",
// 'N' B/S =2 '\0' \u0080 ' ' 'A'
".XXXX XXXXX ...X. ........ X....... ....X ...X.");
// binary skipping over single character
testHighLevelEncodeString("\0a\u{00FF}\u{0080} A",
// B/S =4 '\0' 'a' '\3ff' '\200' ' ' 'A'
"XXXXX ..X.. ........ .XX....X XXXXXXXX X....... ....X ...X.");
// getting into binary mode from digit mode
testHighLevelEncodeString("1234\0",
//D/L '1' '2' '3' '4' U/L B/S =1 \0
"XXXX. ..XX .X.. .X.X .XX. XXX. XXXXX ....X ........"
);
// Create a string in which every character requires binary
let sb = String::new();
for (int i = 0; i <= 3000; i++) {
sb.append((char) (128 + (i % 30)));
}
// Test the output generated by Binary/Switch, particularly near the
// places where the encoding changes: 31, 62, and 2047+31=2078
for (int i : new int[] { 1, 2, 3, 10, 29, 30, 31, 32, 33,
60, 61, 62, 63, 64, 2076, 2077, 2078, 2079, 2080, 2100 }) {
// This is the expected length of a binary string of length "i"
int expectedLength = (8 * i) +
((i <= 31) ? 10 : (i <= 62) ? 20 : (i <= 2078) ? 21 : 31);
// Verify that we are correct about the length.
testHighLevelEncodeString(sb.substring(0, i), expectedLength);
if (i != 1 && i != 32 && i != 2079) {
// The addition of an 'a' at the beginning or end gets merged into the binary code
// in those cases where adding another binary character only adds 8 or 9 bits to the result.
// So we exclude the border cases i=1,32,2079
// A lower case letter at the beginning will be merged into binary mode
testHighLevelEncodeString('a' + sb.substring(0, i - 1), expectedLength);
// A lower case letter at the end will also be merged into binary mode
testHighLevelEncodeString(sb.substring(0, i - 1) + 'a', expectedLength);
}
// A lower case letter at both ends will enough to latch us into LOWER.
testHighLevelEncodeString('a' + sb.substring(0, i) + 'b', expectedLength + 15);
}
sb = new StringBuilder();
for (int i = 0; i < 32; i++) {
sb.append('§'); // § forces binary encoding
}
sb.setCharAt(1, 'A');
// expect B/S(1) A B/S(30)
testHighLevelEncodeString(sb.toString(), 5 + 20 + 31 * 8);
sb = new StringBuilder();
for (int i = 0; i < 31; i++) {
sb.append('§');
}
sb.setCharAt(1, 'A');
// expect B/S(31)
testHighLevelEncodeString(sb.toString(), 10 + 31 * 8);
sb = new StringBuilder();
for (int i = 0; i < 34; i++) {
sb.append('§');
}
sb.setCharAt(1, 'A');
// expect B/S(31) B/S(3)
testHighLevelEncodeString(sb.toString(), 20 + 34 * 8);
sb = new StringBuilder();
for (int i = 0; i < 64; i++) {
sb.append('§');
}
sb.setCharAt(30, 'A');
// expect B/S(64)
testHighLevelEncodeString(sb.toString(), 21 + 64 * 8);
}
#[test]
fn testHighLevelEncodePairs() {
// Typical usage
testHighLevelEncodeString("ABC. DEF\r\n",
// A B C P/S .<sp> D E F P/S \r\n
"...X. ...XX ..X.. ..... ...XX ..X.X ..XX. ..XXX ..... ...X.");
// We should latch to PUNCT mode, rather than shift. Also check all pairs
testHighLevelEncodeString("A. : , \r\n",
// 'A' M/L P/L ". " ": " ", " "\r\n"
"...X. XXX.X XXXX. ...XX ..X.X ..X.. ...X.");
// Latch to DIGIT rather than shift to PUNCT
testHighLevelEncodeString("A. 1234",
// 'A' D/L '.' ' ' '1' '2' '3' '4'
"...X. XXXX. XX.X ...X ..XX .X.. .X.X .X X.");
// Don't bother leaving Binary Shift.
testHighLevelEncodeString("A\200. \200",
// 'A' B/S =2 \200 "." " " \200
"...X. XXXXX ..X.. X....... ..X.XXX. ..X..... X.......");
}
#[test]
#[should_panic]
fn testUserSpecifiedLayers() {
doTestUserSpecifiedLayers(33);
}
#[test]
#[should_panic]
fn testUserSpecifiedLayers2() {
doTestUserSpecifiedLayers(-1);
}
fn doTestUserSpecifiedLayers( userSpecifiedLayers:usize) {
String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
AztecCode aztec = Encoder.encode(alphabet, 25, -2);
assertEquals(2, aztec.getLayers());
assertTrue(aztec.isCompact());
aztec = Encoder.encode(alphabet, 25, 32);
assertEquals(32, aztec.getLayers());
assertFalse(aztec.isCompact());
Encoder.encode(alphabet, 25, userSpecifiedLayers);
}
#[test]
#[should_panic]
fn testBorderCompact4CaseFailed() {
// Compact(4) con hold 608 bits of information, but at most 504 can be data. Rest must
// be error correction
String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// encodes as 26 * 5 * 4 = 520 bits of data
String alphabet4 = alphabet + alphabet + alphabet + alphabet;
Encoder.encode(alphabet4, 0, -4);
}
#[test]
fn testBorderCompact4Case() {
// Compact(4) con hold 608 bits of information, but at most 504 can be data. Rest must
// be error correction
String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// encodes as 26 * 5 * 4 = 520 bits of data
String alphabet4 = alphabet + alphabet + alphabet + alphabet;
// If we just try to encode it normally, it will go to a non-compact 4 layer
AztecCode aztecCode = Encoder.encode(alphabet4, 0, Encoder.DEFAULT_AZTEC_LAYERS);
assertFalse(aztecCode.isCompact());
assertEquals(4, aztecCode.getLayers());
// But shortening the string to 100 bytes (500 bits of data), compact works fine, even if we
// include more error checking.
aztecCode = Encoder.encode(alphabet4.substring(0, 100), 10, Encoder.DEFAULT_AZTEC_LAYERS);
assertTrue(aztecCode.isCompact());
assertEquals(4, aztecCode.getLayers());
}
// Helper routines
fn testEncode( data:&str, compact:bool, layers:usize, expected:&str) {
AztecCode aztec = Encoder.encode(data, 33, Encoder.DEFAULT_AZTEC_LAYERS);
assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix = aztec.getMatrix();
assertEquals("encode() failed", expected, matrix.toString());
}
fn testEncodeDecode( data:&str, compact:bool, layers:usize) {
AztecCode aztec = Encoder.encode(data, 25, Encoder.DEFAULT_AZTEC_LAYERS);
assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix = aztec.getMatrix();
AztecDetectorRXingResult r =
new AztecDetectorRXingResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers());
DecoderRXingResult res = new Decoder().decode(r);
assertEquals(data, res.getText());
// Check error correction by introducing a few minor errors
Random random = getPseudoRandom();
matrix.flip(random.nextInt(matrix.getWidth()), random.nextInt(2));
matrix.flip(random.nextInt(matrix.getWidth()), matrix.getHeight() - 2 + random.nextInt(2));
matrix.flip(random.nextInt(2), random.nextInt(matrix.getHeight()));
matrix.flip(matrix.getWidth() - 2 + random.nextInt(2), random.nextInt(matrix.getHeight()));
r = new AztecDetectorRXingResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers());
res = new Decoder().decode(r);
assertEquals(data, res.getText());
}
fn testWriter( data:&str,
charset:&'static dyn encoding::Encoding,
eccPercent:u32,
compact:bool,
layers:usize) throws FormatException {
// Perform an encode-decode round-trip because it can be lossy.
Map<EncodeHintType,Object> hints = new EnumMap<>(EncodeHintType.class);
if (null != charset) {
hints.put(EncodeHintType.CHARACTER_SET, charset.name());
}
hints.put(EncodeHintType.ERROR_CORRECTION, eccPercent);
AztecWriter writer = new AztecWriter();
BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0, hints);
AztecCode aztec = Encoder.encode(data, eccPercent,
Encoder.DEFAULT_AZTEC_LAYERS, charset);
assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix2 = aztec.getMatrix();
assertEquals(matrix, matrix2);
AztecDetectorRXingResult r =
new AztecDetectorRXingResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers());
DecoderRXingResult res = new Decoder().decode(r);
assertEquals(data, res.getText());
// Check error correction by introducing up to eccPercent/2 errors
int ecWords = aztec.getCodeWords() * eccPercent / 100 / 2;
Random random = getPseudoRandom();
for (int i = 0; i < ecWords; i++) {
// don't touch the core
int x = random.nextBoolean() ?
random.nextInt(aztec.getLayers() * 2)
: matrix.getWidth() - 1 - random.nextInt(aztec.getLayers() * 2);
int y = random.nextBoolean() ?
random.nextInt(aztec.getLayers() * 2)
: matrix.getHeight() - 1 - random.nextInt(aztec.getLayers() * 2);
matrix.flip(x, y);
}
r = new AztecDetectorRXingResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers());
res = new Decoder().decode(r);
assertEquals(data, res.getText());
}
fn getPseudoRandom() -> rand::Rng{
return new Random(0xDEADBEEF);
}
fn testModeMessage( compact:bool, layers:usize, words:usize, expected:&str) {
BitArray in = Encoder.generateModeMessage(compact, layers, words);
assertEquals("generateModeMessage() failed", stripSpace(expected), stripSpace(in.toString()));
}
fn testStuffBits( wordSize:usize, bits:&str, expected:&str) {
BitArray in = toBitArray(bits);
BitArray stuffed = Encoder.stuffBits(in, wordSize);
assertEquals("stuffBits() failed for input string: " + bits,
stripSpace(expected), stripSpace(stuffed.toString()));
}
fn testHighLevelEncodeString( s:&str, expectedBits:&str) {
BitArray bits = new HighLevelEncoder(s.getBytes(StandardCharsets.ISO_8859_1)).encode();
String receivedBits = stripSpace(bits.toString());
assertEquals("highLevelEncode() failed for input string: " + s, stripSpace(expectedBits), receivedBits);
assertEquals(s, Decoder.highLevelDecode(toBooleanArray(bits)));
}
fn testHighLevelEncodeString( s:&str, expectedReceivedBits:u32) {
BitArray bits = new HighLevelEncoder(s.getBytes(StandardCharsets.ISO_8859_1)).encode();
int receivedBitCount = stripSpace(bits.toString()).length();
assertEquals("highLevelEncode() failed for input string: " + s,
expectedReceivedBits, receivedBitCount);
assertEquals(s, Decoder.highLevelDecode(toBooleanArray(bits)));
}

572
src/aztec/decoder.rs Normal file
View File

@@ -0,0 +1,572 @@
/*
* 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.
*/
// package com.google.zxing.aztec.decoder;
// import com.google.zxing.FormatException;
// import com.google.zxing.aztec.AztecDetectorRXingResult;
// import com.google.zxing.common.BitMatrix;
// import com.google.zxing.common.CharacterSetECI;
// import com.google.zxing.common.DecoderRXingResult;
// import com.google.zxing.common.reedsolomon.GenericGF;
// import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
// import com.google.zxing.common.reedsolomon.ReedSolomonException;
// import java.io.ByteArrayOutputStream;
// import java.io.UnsupportedEncodingException;
// import java.nio.charset.Charset;
// import java.nio.charset.StandardCharsets;
// import java.util.Arrays;
use encoding::Encoding;
use crate::{
common::{
self,
reedsolomon::{
get_predefined_genericgf, GenericGF, PredefinedGenericGF, ReedSolomonDecoder,
},
BitMatrix, CharacterSetECI, DecoderRXingResult, DetectorRXingResult,
},
exceptions::Exceptions,
};
use super::AztecDetectorResult::AztecDetectorRXingResult;
/**
* <p>The main class which implements Aztec Code decoding -- as opposed to locating and extracting
* the Aztec Code from an image.</p>
*
* @author David Olivier
*/
#[derive(PartialEq, Eq,Copy,Clone)]
enum Table {
UPPER,
LOWER,
MIXED,
DIGIT,
PUNCT,
BINARY,
}
const UPPER_TABLE: [&str; 32] = [
"CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS",
];
const LOWER_TABLE: [&str; 32] = [
"CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS",
];
const MIXED_TABLE: [&str; 32] = [
"CTRL_PS", " ", "\u{1}", "\u{2}", "\u{3}", "\u{4}", "\u{5}", "\u{6}", "\u{7}", "\u{8}", "\t",
"\n", "\u{13}", "\u{0c}", "\r", "\u{33}", "\u{34}", "\u{35}", "\u{36}", "\u{37}", "@", "\\",
"^", "_", "`", "|", "~", "\u{177}", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS",
];
const PUNCT_TABLE: [&str; 32] = [
"FLG(n)", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*",
"+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL",
];
const DIGIT_TABLE: [&str; 16] = [
"CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL",
"CTRL_US",
];
// private static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
// private AztecDetectorRXingResult ddata;
pub fn decode(
detectorRXingResult: &AztecDetectorRXingResult,
) -> Result<DecoderRXingResult, Exceptions> {
//let mut detectorRXingResult = detectorRXingResult.clone();
let matrix = detectorRXingResult.getBits();
let rawbits = extractBits(&detectorRXingResult, matrix);
let correctedBits = correctBits(&detectorRXingResult, &rawbits)?;
let rawBytes = convertBoolArrayToByteArray(&correctedBits.correctBits);
let result = getEncodedData(&correctedBits.correctBits);
let mut decoderRXingResult = DecoderRXingResult::new(
rawBytes,
result?,
Vec::new(),
format!("{}%", correctedBits.ecLevel),
);
decoderRXingResult.setNumBits(correctedBits.correctBits.len());
Ok(decoderRXingResult)
}
/// This method is used for testing the high-level encoder
pub fn highLevelDecode(correctedBits: &[bool]) -> Result<String, Exceptions> {
getEncodedData(correctedBits)
}
/**
* Gets the string encoded in the aztec code bits
*
* @return the decoded string
*/
fn getEncodedData(correctedBits: &[bool]) -> Result<String, Exceptions> {
let endIndex = correctedBits.len();
let mut latchTable = Table::UPPER; // table most recently latched to
let mut shiftTable = Table::UPPER; // table to use for the next read
// Final decoded string result
// (correctedBits-5) / 4 is an upper bound on the size (all-digit result)
let mut result = String::with_capacity((correctedBits.len() - 5) / 4);
// Intermediary buffer of decoded bytes, which is decoded into a string and flushed
// when character encoding changes (ECI) or input ends.
let mut decodedBytes: Vec<u8> = Vec::new();
let mut encdr: &'static dyn encoding::Encoding = encoding::all::UTF_8;
let mut index = 0;
while index < endIndex {
if shiftTable == Table::BINARY {
if endIndex - index < 5 {
break;
}
let mut length = readCode(correctedBits, index, 5);
index += 5;
if length == 0 {
if endIndex - index < 11 {
break;
}
length = readCode(correctedBits, index, 11) + 31;
index += 11;
}
for _charCount in 0..length {
// for (int charCount = 0; charCount < length; charCount++) {
if endIndex - index < 8 {
index = endIndex; // Force outer loop to exit
break;
}
let code = readCode(correctedBits, index, 8);
decodedBytes.push(code as u8);
index += 8;
}
// Go back to whatever mode we had been in
shiftTable = latchTable;
} else {
let size = if shiftTable == Table::DIGIT { 4 } else { 5 };
if endIndex - index < size {
break;
}
let code = readCode(correctedBits, index, size);
index += size;
let str = getCharacter(shiftTable, code)?;
if "FLG(n)" == str {
if endIndex - index < 3 {
break;
}
let mut n = readCode(correctedBits, index, 3);
index += 3;
// flush bytes, FLG changes state
// try {
result.push_str(&String::from_utf8(decodedBytes.clone()).unwrap());
// result.append(decodedBytes.toString(encoding.name()));
// } catch (UnsupportedEncodingException uee) {
// throw new IllegalStateException(uee);
// }
decodedBytes.clear();
match n {
0 => result.push(29 as char), // translate FNC1 as ASCII 29
7 => {
return Err(Exceptions::FormatException(
"FLG(7) is reserved and illegal".to_owned(),
))
} // FLG(7) is reserved and illegal
_ => {
// ECI is decimal integer encoded as 1-6 codes in DIGIT mode
let mut eci = 0;
if endIndex - index < 4 * (n as usize) {
break;
}
while n > 0 {
//while (n-- > 0) {
let nextDigit = readCode(correctedBits, index, 4);
index += 4;
if nextDigit < 2 || nextDigit > 11 {
return Err(Exceptions::FormatException(
"Not a decimal digit".to_owned(),
)); // Not a decimal digit
}
eci = eci * 10 + (nextDigit - 2);
n -= 1;
}
let charsetECI = CharacterSetECI::getCharacterSetECIByValue(eci);
if charsetECI.is_err() {
return Err(Exceptions::FormatException(
"Charset must exist".to_owned(),
));
}
encdr = CharacterSetECI::getCharset(&charsetECI?);
// encdr = charsetECI?::getCharset();
}
}
// switch (n) {
// case 0:
// result.append(29 as char); // translate FNC1 as ASCII 29
// break;
// case 7:
// throw FormatException.getFormatInstance(); // FLG(7) is reserved and illegal
// default:
// // ECI is decimal integer encoded as 1-6 codes in DIGIT mode
// int eci = 0;
// if (endIndex - index < 4 * n) {
// break;
// }
// while (n-- > 0) {
// int nextDigit = readCode(correctedBits, index, 4);
// index += 4;
// if (nextDigit < 2 || nextDigit > 11) {
// throw FormatException.getFormatInstance(); // Not a decimal digit
// }
// eci = eci * 10 + (nextDigit - 2);
// }
// CharacterSetECI charsetECI = CharacterSetECI.getCharacterSetECIByValue(eci);
// if (charsetECI == null) {
// throw FormatException.getFormatInstance();
// }
// encoding = charsetECI.getCharset();
// }
// Go back to whatever mode we had been in
shiftTable = latchTable;
} else if str.starts_with("CTRL_") {
// Table changes
// ISO/IEC 24778:2008 prescribes ending a shift sequence in the mode from which it was invoked.
// That's including when that mode is a shift.
// Our test case dlusbs.png for issue #642 exercises that.
latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S
shiftTable = getTable(str.chars().nth(5).unwrap());
if str.chars().nth(6).unwrap() == 'L' {
latchTable = shiftTable;
}
} else {
// Though stored as a table of strings for convenience, codes actually represent 1 or 2 *bytes*.
let b = str.as_bytes();
//let b = str.getBytes(StandardCharsets.US_ASCII);
//decodedBytes.write(b, 0, b.length);
for bt in b {
decodedBytes.push(*bt);
}
// Go back to whatever mode we had been in
shiftTable = latchTable;
}
}
}
//try {
if let Ok(str) = encdr.decode(&decodedBytes, encoding::DecoderTrap::Strict) {
result.push_str(&str);
} else {
return Err(Exceptions::IllegalStateException("bad encoding".to_owned()));
}
// result.push_str(decodedBytes.toString(encoding.name()));
//} catch (UnsupportedEncodingException uee) {
// can't happen
//throw new IllegalStateException(uee);
//}
Ok(result)
}
/**
* gets the table corresponding to the char passed
*/
fn getTable(t: char) -> Table {
match t {
'L' => Table::LOWER,
'P' => Table::PUNCT,
'M' => Table::MIXED,
'D' => Table::DIGIT,
'B' => Table::BINARY,
_ => Table::UPPER,
}
// switch (t) {
// case 'L':
// return Table.LOWER;
// case 'P':
// return Table.PUNCT;
// case 'M':
// return Table.MIXED;
// case 'D':
// return Table.DIGIT;
// case 'B':
// return Table.BINARY;
// case 'U':
// default:
// return Table.UPPER;
// }
}
/**
* Gets the character (or string) corresponding to the passed code in the given table
*
* @param table the table used
* @param code the code of the character
*/
fn getCharacter(table: Table, code: u32) -> Result<&'static str, Exceptions> {
match table {
Table::UPPER => Ok(UPPER_TABLE[code as usize]),
Table::LOWER => Ok(LOWER_TABLE[code as usize]),
Table::MIXED => Ok(MIXED_TABLE[code as usize]),
Table::DIGIT => Ok(DIGIT_TABLE[code as usize]),
Table::PUNCT => Ok(PUNCT_TABLE[code as usize]),
_ => Err(Exceptions::IllegalStateException("Bad table".to_owned())),
}
// switch (table) {
// case UPPER:
// return UPPER_TABLE[code];
// case LOWER:
// return LOWER_TABLE[code];
// case MIXED:
// return MIXED_TABLE[code];
// case PUNCT:
// return PUNCT_TABLE[code];
// case DIGIT:
// return DIGIT_TABLE[code];
// default:
// // Should not reach here.
// throw new IllegalStateException("Bad table");
}
struct CorrectedBitsRXingResult {
correctBits: Vec<bool>,
ecLevel: u32,
}
impl CorrectedBitsRXingResult {
pub fn new(correctBits: Vec<bool>, ecLevel: u32) -> Self {
Self {
correctBits,
ecLevel,
}
}
}
/**
* <p>Performs RS error correction on an array of bits.</p>
*
* @return the corrected array
* @throws FormatException if the input contains too many errors
*/
fn correctBits(
ddata: &&AztecDetectorRXingResult,
rawbits: &[bool],
) -> Result<CorrectedBitsRXingResult, Exceptions> {
let gf: GenericGF;
let codewordSize;
if ddata.getNbLayers() <= 2 {
codewordSize = 6;
gf = get_predefined_genericgf(PredefinedGenericGF::AztecData6); //GenericGF.AZTEC_DATA_6;
} else if ddata.getNbLayers() <= 8 {
codewordSize = 8;
gf = get_predefined_genericgf(PredefinedGenericGF::AztecData8); //GenericGF.AZTEC_DATA_8;
} else if ddata.getNbLayers() <= 22 {
codewordSize = 10;
gf = get_predefined_genericgf(PredefinedGenericGF::AztecData10); //GenericGF.AZTEC_DATA_10;
} else {
codewordSize = 12;
gf = get_predefined_genericgf(PredefinedGenericGF::AztecData12); //GenericGF.AZTEC_DATA_12;
}
let numDataCodewords = ddata.getNbDatablocks();
let numCodewords = rawbits.len() / codewordSize;
if numCodewords < numDataCodewords as usize {
return Err(Exceptions::FormatException(format!(
"numCodewords {}< numDataCodewords{}",
numCodewords, numDataCodewords
)));
}
let mut offset = rawbits.len() % codewordSize;
let mut dataWords = vec![0i32; numCodewords];
for i in 0..numCodewords {
// for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
dataWords[i] = readCode(rawbits, offset, codewordSize) as i32;
offset += codewordSize;
}
//try {
let rsDecoder = ReedSolomonDecoder::new(gf);
rsDecoder.decode(
&mut dataWords,
(numCodewords - numDataCodewords as usize) as i32,
);
//} catch (ReedSolomonException ex) {
//throw FormatException.getFormatInstance(ex);
//}
// Now perform the unstuffing operation.
// First, count how many bits are going to be thrown out as stuffing
let mask = (1 << codewordSize) - 1;
let mut stuffedBits = 0;
for i in 0..numDataCodewords as usize {
// for (int i = 0; i < numDataCodewords; i++) {
let dataWord = dataWords[i];
if dataWord == 0 || dataWord == mask {
return Err(Exceptions::FormatException(
"dataWord == 0 || dataWord == mask".to_owned(),
));
//throw FormatException.getFormatInstance();
} else if dataWord == 1 || dataWord == mask - 1 {
stuffedBits += 1;
}
}
// Now, actually unpack the bits and remove the stuffing
let mut correctedBits =
vec![false; (numDataCodewords * codewordSize as u32 - stuffedBits) as usize];
let mut index = 0;
for i in 0..numDataCodewords as usize {
// for (int i = 0; i < numDataCodewords; i++) {
let dataWord = dataWords[i];
if dataWord == 1 || dataWord == mask - 1 {
// next codewordSize-1 bits are all zeros or all ones
correctedBits.splice(
index..index + codewordSize - 1,
vec![dataWord > 1; codewordSize],
);
// Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
index += codewordSize - 1;
} else {
for bit in (0..codewordSize).rev() {
// for (int bit = codewordSize - 1; bit >= 0; --bit) {
correctedBits[index] = (dataWord & (1 << bit)) != 0;
index += 1;
}
}
}
Ok(CorrectedBitsRXingResult::new(
correctedBits,
(100 * (numCodewords - numDataCodewords as usize) / numCodewords) as u32,
))
}
/**
* Gets the array of bits from an Aztec Code matrix
*
* @return the array of bits
*/
fn extractBits(ddata: &AztecDetectorRXingResult, matrix: &BitMatrix) -> Vec<bool> {
let compact = ddata.isCompact();
let layers = ddata.getNbLayers();
let baseMatrixSize = ((if compact { 11 } else { 14 }) + layers * 4) as usize; // not including alignment lines
let mut alignmentMap = vec![0u32; baseMatrixSize];
let mut rawbits = vec![false; totalBitsInLayer(layers as usize, compact)];
if compact {
for i in 0..alignmentMap.len() {
// for (int i = 0; i < alignmentMap.length; i++) {
alignmentMap[i] = i as u32;
}
} else {
let matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
let origCenter = baseMatrixSize / 2;
let center = matrixSize / 2;
for i in 0..origCenter {
// for (int i = 0; i < origCenter; i++) {
let newOffset = i + i / 15;
alignmentMap[origCenter - i - 1] = (center - newOffset - 1) as u32;
alignmentMap[origCenter + i] = (center + newOffset + 1) as u32;
}
}
let mut rowOffset = 0;
for i in 0..layers {
// for (int i = 0, rowOffset = 0; i < layers; i++) {
let rowSize = (layers - i) * 4 + (if compact { 9 } else { 12 });
// The top-left most point of this layer is <low, low> (not including alignment lines)
let low = i * 2;
// The bottom-right most point of this layer is <high, high> (not including alignment lines)
let high = baseMatrixSize as u32 - 1 - low;
// We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
for j in 0..rowSize {
// for (int j = 0; j < rowSize; j++) {
let columnOffset = j * 2;
for k in 0..2 {
// for (int k = 0; k < 2; k++) {
// left column
rawbits[(rowOffset + columnOffset + k) as usize] = matrix.get(
alignmentMap[(low + k) as usize],
alignmentMap[(low + j) as usize],
);
// bottom row
rawbits[(rowOffset + 2 * rowSize + columnOffset + k) as usize] = matrix.get(
alignmentMap[(low + j) as usize],
alignmentMap[(high - k) as usize],
);
// right column
rawbits[(rowOffset + 4 * rowSize + columnOffset + k) as usize] = matrix.get(
alignmentMap[(high - k) as usize],
alignmentMap[(high - j) as usize],
);
// top row
rawbits[(rowOffset + 6 * rowSize + columnOffset + k) as usize] = matrix.get(
alignmentMap[(high - j) as usize],
alignmentMap[(low + k) as usize],
);
}
}
rowOffset += rowSize * 8;
}
return rawbits;
}
/**
* Reads a code of given length and at given index in an array of bits
*/
fn readCode(rawbits: &[bool], startIndex: usize, length: usize) -> u32 {
let mut res = 0;
for i in startIndex..length {
// for (int i = startIndex; i < startIndex + length; i++) {
res <<= 1;
if rawbits[i] {
res |= 0x01;
}
}
return res;
}
/**
* Reads a code of length 8 in an array of bits, padding with zeros
*/
fn readByte(rawbits: &[bool], startIndex: usize) -> u8 {
let n = rawbits.len() - startIndex;
if n >= 8 {
return readCode(rawbits, startIndex, 8) as u8;
}
return (readCode(rawbits, startIndex, n) << (8 - n)) as u8;
}
/**
* Packs a bit array into bytes, most significant bit first
*/
pub fn convertBoolArrayToByteArray(boolArr: &[bool]) -> Vec<u8> {
let mut byteArr = vec![0u8; (boolArr.len() + 7) / 8];
for i in 0..byteArr.len() {
// for (int i = 0; i < byteArr.length; i++) {
byteArr[i] = readByte(boolArr, 8 * i);
}
return byteArr;
}
fn totalBitsInLayer(layers: usize, compact: bool) -> usize {
(if compact { 88 } else { 112 } + 16 * layers) * layers
// return ((compact ? 88 : 112) + 16 * layers) * layers;
}

View File

@@ -1,443 +0,0 @@
/*
* 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.
*/
package com.google.zxing.aztec.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.aztec.AztecDetectorRXingResult;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.CharacterSetECI;
import com.google.zxing.common.DecoderRXingResult;
import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* <p>The main class which implements Aztec Code decoding -- as opposed to locating and extracting
* the Aztec Code from an image.</p>
*
* @author David Olivier
*/
public final class Decoder {
private enum Table {
UPPER,
LOWER,
MIXED,
DIGIT,
PUNCT,
BINARY
}
private static final String[] UPPER_TABLE = {
"CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
};
private static final String[] LOWER_TABLE = {
"CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
};
private static final String[] MIXED_TABLE = {
"CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
"\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
"`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
};
private static final String[] PUNCT_TABLE = {
"FLG(n)", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
"*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
};
private static final String[] DIGIT_TABLE = {
"CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
};
private static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
private AztecDetectorRXingResult ddata;
public DecoderRXingResult decode(AztecDetectorRXingResult detectorRXingResult) throws FormatException {
ddata = detectorRXingResult;
BitMatrix matrix = detectorRXingResult.getBits();
boolean[] rawbits = extractBits(matrix);
CorrectedBitsRXingResult correctedBits = correctBits(rawbits);
byte[] rawBytes = convertBoolArrayToByteArray(correctedBits.correctBits);
String result = getEncodedData(correctedBits.correctBits);
DecoderRXingResult decoderRXingResult =
new DecoderRXingResult(rawBytes, result, null, String.format("%d%%", correctedBits.ecLevel));
decoderRXingResult.setNumBits(correctedBits.correctBits.length);
return decoderRXingResult;
}
// This method is used for testing the high-level encoder
public static String highLevelDecode(boolean[] correctedBits) throws FormatException {
return getEncodedData(correctedBits);
}
/**
* Gets the string encoded in the aztec code bits
*
* @return the decoded string
*/
private static String getEncodedData(boolean[] correctedBits) throws FormatException {
int endIndex = correctedBits.length;
Table latchTable = Table.UPPER; // table most recently latched to
Table shiftTable = Table.UPPER; // table to use for the next read
// Final decoded string result
// (correctedBits-5) / 4 is an upper bound on the size (all-digit result)
StringBuilder result = new StringBuilder((correctedBits.length - 5) / 4);
// Intermediary buffer of decoded bytes, which is decoded into a string and flushed
// when character encoding changes (ECI) or input ends.
ByteArrayOutputStream decodedBytes = new ByteArrayOutputStream();
Charset encoding = DEFAULT_ENCODING;
int index = 0;
while (index < endIndex) {
if (shiftTable == Table.BINARY) {
if (endIndex - index < 5) {
break;
}
int length = readCode(correctedBits, index, 5);
index += 5;
if (length == 0) {
if (endIndex - index < 11) {
break;
}
length = readCode(correctedBits, index, 11) + 31;
index += 11;
}
for (int charCount = 0; charCount < length; charCount++) {
if (endIndex - index < 8) {
index = endIndex; // Force outer loop to exit
break;
}
int code = readCode(correctedBits, index, 8);
decodedBytes.write((byte) code);
index += 8;
}
// Go back to whatever mode we had been in
shiftTable = latchTable;
} else {
int size = shiftTable == Table.DIGIT ? 4 : 5;
if (endIndex - index < size) {
break;
}
int code = readCode(correctedBits, index, size);
index += size;
String str = getCharacter(shiftTable, code);
if ("FLG(n)".equals(str)) {
if (endIndex - index < 3) {
break;
}
int n = readCode(correctedBits, index, 3);
index += 3;
// flush bytes, FLG changes state
try {
result.append(decodedBytes.toString(encoding.name()));
} catch (UnsupportedEncodingException uee) {
throw new IllegalStateException(uee);
}
decodedBytes.reset();
switch (n) {
case 0:
result.append((char) 29); // translate FNC1 as ASCII 29
break;
case 7:
throw FormatException.getFormatInstance(); // FLG(7) is reserved and illegal
default:
// ECI is decimal integer encoded as 1-6 codes in DIGIT mode
int eci = 0;
if (endIndex - index < 4 * n) {
break;
}
while (n-- > 0) {
int nextDigit = readCode(correctedBits, index, 4);
index += 4;
if (nextDigit < 2 || nextDigit > 11) {
throw FormatException.getFormatInstance(); // Not a decimal digit
}
eci = eci * 10 + (nextDigit - 2);
}
CharacterSetECI charsetECI = CharacterSetECI.getCharacterSetECIByValue(eci);
if (charsetECI == null) {
throw FormatException.getFormatInstance();
}
encoding = charsetECI.getCharset();
}
// Go back to whatever mode we had been in
shiftTable = latchTable;
} else if (str.startsWith("CTRL_")) {
// Table changes
// ISO/IEC 24778:2008 prescribes ending a shift sequence in the mode from which it was invoked.
// That's including when that mode is a shift.
// Our test case dlusbs.png for issue #642 exercises that.
latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S
shiftTable = getTable(str.charAt(5));
if (str.charAt(6) == 'L') {
latchTable = shiftTable;
}
} else {
// Though stored as a table of strings for convenience, codes actually represent 1 or 2 *bytes*.
byte[] b = str.getBytes(StandardCharsets.US_ASCII);
decodedBytes.write(b, 0, b.length);
// Go back to whatever mode we had been in
shiftTable = latchTable;
}
}
}
try {
result.append(decodedBytes.toString(encoding.name()));
} catch (UnsupportedEncodingException uee) {
// can't happen
throw new IllegalStateException(uee);
}
return result.toString();
}
/**
* gets the table corresponding to the char passed
*/
private static Table getTable(char t) {
switch (t) {
case 'L':
return Table.LOWER;
case 'P':
return Table.PUNCT;
case 'M':
return Table.MIXED;
case 'D':
return Table.DIGIT;
case 'B':
return Table.BINARY;
case 'U':
default:
return Table.UPPER;
}
}
/**
* Gets the character (or string) corresponding to the passed code in the given table
*
* @param table the table used
* @param code the code of the character
*/
private static String getCharacter(Table table, int code) {
switch (table) {
case UPPER:
return UPPER_TABLE[code];
case LOWER:
return LOWER_TABLE[code];
case MIXED:
return MIXED_TABLE[code];
case PUNCT:
return PUNCT_TABLE[code];
case DIGIT:
return DIGIT_TABLE[code];
default:
// Should not reach here.
throw new IllegalStateException("Bad table");
}
}
static final class CorrectedBitsRXingResult {
private final boolean[] correctBits;
private final int ecLevel;
CorrectedBitsRXingResult(boolean[] correctBits, int ecLevel) {
this.correctBits = correctBits;
this.ecLevel = ecLevel;
}
}
/**
* <p>Performs RS error correction on an array of bits.</p>
*
* @return the corrected array
* @throws FormatException if the input contains too many errors
*/
private CorrectedBitsRXingResult correctBits(boolean[] rawbits) throws FormatException {
GenericGF gf;
int codewordSize;
if (ddata.getNbLayers() <= 2) {
codewordSize = 6;
gf = GenericGF.AZTEC_DATA_6;
} else if (ddata.getNbLayers() <= 8) {
codewordSize = 8;
gf = GenericGF.AZTEC_DATA_8;
} else if (ddata.getNbLayers() <= 22) {
codewordSize = 10;
gf = GenericGF.AZTEC_DATA_10;
} else {
codewordSize = 12;
gf = GenericGF.AZTEC_DATA_12;
}
int numDataCodewords = ddata.getNbDatablocks();
int numCodewords = rawbits.length / codewordSize;
if (numCodewords < numDataCodewords) {
throw FormatException.getFormatInstance();
}
int offset = rawbits.length % codewordSize;
int[] dataWords = new int[numCodewords];
for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
dataWords[i] = readCode(rawbits, offset, codewordSize);
}
try {
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
rsDecoder.decode(dataWords, numCodewords - numDataCodewords);
} catch (ReedSolomonException ex) {
throw FormatException.getFormatInstance(ex);
}
// Now perform the unstuffing operation.
// First, count how many bits are going to be thrown out as stuffing
int mask = (1 << codewordSize) - 1;
int stuffedBits = 0;
for (int i = 0; i < numDataCodewords; i++) {
int dataWord = dataWords[i];
if (dataWord == 0 || dataWord == mask) {
throw FormatException.getFormatInstance();
} else if (dataWord == 1 || dataWord == mask - 1) {
stuffedBits++;
}
}
// Now, actually unpack the bits and remove the stuffing
boolean[] correctedBits = new boolean[numDataCodewords * codewordSize - stuffedBits];
int index = 0;
for (int i = 0; i < numDataCodewords; i++) {
int dataWord = dataWords[i];
if (dataWord == 1 || dataWord == mask - 1) {
// next codewordSize-1 bits are all zeros or all ones
Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
index += codewordSize - 1;
} else {
for (int bit = codewordSize - 1; bit >= 0; --bit) {
correctedBits[index++] = (dataWord & (1 << bit)) != 0;
}
}
}
return new CorrectedBitsRXingResult(correctedBits, 100 * (numCodewords - numDataCodewords) / numCodewords);
}
/**
* Gets the array of bits from an Aztec Code matrix
*
* @return the array of bits
*/
private boolean[] extractBits(BitMatrix matrix) {
boolean compact = ddata.isCompact();
int layers = ddata.getNbLayers();
int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
int[] alignmentMap = new int[baseMatrixSize];
boolean[] rawbits = new boolean[totalBitsInLayer(layers, compact)];
if (compact) {
for (int i = 0; i < alignmentMap.length; i++) {
alignmentMap[i] = i;
}
} else {
int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
int origCenter = baseMatrixSize / 2;
int center = matrixSize / 2;
for (int i = 0; i < origCenter; i++) {
int newOffset = i + i / 15;
alignmentMap[origCenter - i - 1] = center - newOffset - 1;
alignmentMap[origCenter + i] = center + newOffset + 1;
}
}
for (int i = 0, rowOffset = 0; i < layers; i++) {
int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
// The top-left most point of this layer is <low, low> (not including alignment lines)
int low = i * 2;
// The bottom-right most point of this layer is <high, high> (not including alignment lines)
int high = baseMatrixSize - 1 - low;
// We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
for (int j = 0; j < rowSize; j++) {
int columnOffset = j * 2;
for (int k = 0; k < 2; k++) {
// left column
rawbits[rowOffset + columnOffset + k] =
matrix.get(alignmentMap[low + k], alignmentMap[low + j]);
// bottom row
rawbits[rowOffset + 2 * rowSize + columnOffset + k] =
matrix.get(alignmentMap[low + j], alignmentMap[high - k]);
// right column
rawbits[rowOffset + 4 * rowSize + columnOffset + k] =
matrix.get(alignmentMap[high - k], alignmentMap[high - j]);
// top row
rawbits[rowOffset + 6 * rowSize + columnOffset + k] =
matrix.get(alignmentMap[high - j], alignmentMap[low + k]);
}
}
rowOffset += rowSize * 8;
}
return rawbits;
}
/**
* Reads a code of given length and at given index in an array of bits
*/
private static int readCode(boolean[] rawbits, int startIndex, int length) {
int res = 0;
for (int i = startIndex; i < startIndex + length; i++) {
res <<= 1;
if (rawbits[i]) {
res |= 0x01;
}
}
return res;
}
/**
* Reads a code of length 8 in an array of bits, padding with zeros
*/
private static byte readByte(boolean[] rawbits, int startIndex) {
int n = rawbits.length - startIndex;
if (n >= 8) {
return (byte) readCode(rawbits, startIndex, 8);
}
return (byte) (readCode(rawbits, startIndex, n) << (8 - n));
}
/**
* Packs a bit array into bytes, most significant bit first
*/
static byte[] convertBoolArrayToByteArray(boolean[] boolArr) {
byte[] byteArr = new byte[(boolArr.length + 7) / 8];
for (int i = 0; i < byteArr.length; i++) {
byteArr[i] = readByte(boolArr, 8 * i);
}
return byteArr;
}
private static int totalBitsInLayer(int layers, boolean compact) {
return ((compact ? 88 : 112) + 16 * layers) * layers;
}
}

View File

@@ -14,18 +14,27 @@
* limitations under the License.
*/
package com.google.zxing.aztec.detector;
// package com.google.zxing.aztec.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.RXingResultPoint;
import com.google.zxing.aztec.AztecDetectorRXingResult;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.GridSampler;
import com.google.zxing.common.detector.MathUtils;
import com.google.zxing.common.detector.WhiteRectangleDetector;
import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;
// import com.google.zxing.NotFoundException;
// import com.google.zxing.RXingResultPoint;
// import com.google.zxing.aztec.AztecDetectorRXingResult;
// import com.google.zxing.common.BitMatrix;
// import com.google.zxing.common.GridSampler;
// import com.google.zxing.common.detector.MathUtils;
// import com.google.zxing.common.detector.WhiteRectangleDetector;
// import com.google.zxing.common.reedsolomon.GenericGF;
// import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
// import com.google.zxing.common.reedsolomon.ReedSolomonException;
use crate::common::BitMatrix;
const EXPECTED_CORNER_BITS : [u16;4]= [
0xee0, // 07340 XXX .XX X.. ...
0x1dc, // 00734 ... XXX .XX X..
0x83b, // 04073 X.. ... XXX .XX
0x707, // 03407 .XX X.. ... XXX
];
/**
* Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code
@@ -34,25 +43,27 @@ import com.google.zxing.common.reedsolomon.ReedSolomonException;
* @author David Olivier
* @author Frank Yellin
*/
public final class Detector {
pub struct Detector {
image:BitMatrix,
private static final int[] EXPECTED_CORNER_BITS = {
0xee0, // 07340 XXX .XX X.. ...
0x1dc, // 00734 ... XXX .XX X..
0x83b, // 04073 X.. ... XXX .XX
0x707, // 03407 .XX X.. ... XXX
};
compact:bool,
nbLayers:u32,
nbDataBlocks:u32,
nbCenterLayers:u32,
shift:u32,
}
private final BitMatrix image;
impl Detector {
private boolean compact;
private int nbLayers;
private int nbDataBlocks;
private int nbCenterLayers;
private int shift;
public Detector(BitMatrix image) {
this.image = image;
pub fn new( image:BitMatrix) -> Self{
Self{
image,
compact: false,
nbLayers: 0,
nbDataBlocks: 0,
nbCenterLayers: 0,
shift: 0,
}
}
public AztecDetectorRXingResult detect() throws NotFoundException {

10
src/aztec/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
mod AztecDetectorResult;
pub mod decoder;
// pub mod detector;
#[cfg(test)]
mod DecoderTest;
// #[cfg(test)]
// mod EncoderTest;
mod shared_test_methods;

View File

@@ -0,0 +1,32 @@
use regex::Regex;
use crate::common::BitArray;
const SPACES :&str= "\\s+";
const DOTX : &str = "[^.X]";
pub fn toBitArray( bits:&str) -> BitArray{
let mut ba_in = BitArray::new();
let replacer_regex = Regex::new(DOTX).unwrap();
let str = replacer_regex.replace_all(bits, "");
for aStr in str.chars() {
// for (char aStr : str) {
ba_in.appendBit(aStr == 'X');
}
ba_in
}
pub fn toBooleanArray( bitArray:&BitArray) ->Vec<bool>{
let mut result = vec![false;bitArray.getSize()];
for i in 0..result.len() {
// for (int i = 0; i < result.length; i++) {
result[i] = bitArray.get(i);
}
result
}
pub fn stripSpace( s:&str) -> String{
let replacer_regex = Regex::new(SPACES).unwrap();
replacer_regex.replace_all(s, "").to_string()
}