From fb08ee0e341bd9c5a782f6eb988da6f581b5e8b0 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 23 Sep 2022 17:09:46 -0500 Subject: [PATCH] continued progress on aztec, no pass --- src/aztec/AztecReader.java | 123 ---- src/aztec/AztecWriter.java | 94 --- src/aztec/DetectorTest.rs | 270 +++++---- src/aztec/EncoderTest.rs | 592 ++++++++++--------- src/aztec/aztec_reader.rs | 123 ++++ src/aztec/aztec_writer.rs | 157 +++++ src/aztec/detector.rs | 2 +- src/aztec/encoder/Encoder.java | 404 ------------- src/aztec/encoder/aztec_code.rs | 10 + src/aztec/encoder/binary_shift_token.rs | 4 +- src/aztec/encoder/encoder.rs | 523 +++++++++++++++++ src/aztec/encoder/high_level_encoder.rs | 740 +++++++++++++----------- src/aztec/encoder/mod.rs | 2 + src/aztec/encoder/simple_token.rs | 6 +- src/aztec/encoder/state.rs | 368 ++++++------ src/aztec/encoder/token.rs | 90 ++- src/aztec/mod.rs | 13 +- src/common/mod.rs | 48 +- src/lib.rs | 434 +++++++++++--- 19 files changed, 2379 insertions(+), 1624 deletions(-) delete mode 100644 src/aztec/AztecReader.java delete mode 100644 src/aztec/AztecWriter.java create mode 100644 src/aztec/aztec_reader.rs create mode 100644 src/aztec/aztec_writer.rs delete mode 100644 src/aztec/encoder/Encoder.java create mode 100644 src/aztec/encoder/encoder.rs diff --git a/src/aztec/AztecReader.java b/src/aztec/AztecReader.java deleted file mode 100644 index a0c1dfe..0000000 --- a/src/aztec/AztecReader.java +++ /dev/null @@ -1,123 +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.BarcodeFormat; -import com.google.zxing.BinaryBitmap; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.Reader; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.RXingResultPointCallback; -import com.google.zxing.aztec.decoder.Decoder; -import com.google.zxing.aztec.detector.Detector; -import com.google.zxing.common.DecoderRXingResult; - -import java.util.List; -import java.util.Map; - -/** - * This implementation can detect and decode Aztec codes in an image. - * - * @author David Olivier - */ -public final class AztecReader implements Reader { - - /** - * Locates and decodes a Data Matrix code in an image. - * - * @return a String representing the content encoded by the Data Matrix code - * @throws NotFoundException if a Data Matrix code cannot be found - * @throws FormatException if a Data Matrix code cannot be decoded - */ - @Override - public RXingResult decode(BinaryBitmap image) throws NotFoundException, FormatException { - return decode(image, null); - } - - @Override - public RXingResult decode(BinaryBitmap image, Map hints) - throws NotFoundException, FormatException { - - NotFoundException notFoundException = null; - FormatException formatException = null; - Detector detector = new Detector(image.getBlackMatrix()); - RXingResultPoint[] points = null; - DecoderRXingResult decoderRXingResult = null; - try { - AztecDetectorRXingResult detectorRXingResult = detector.detect(false); - points = detectorRXingResult.getPoints(); - decoderRXingResult = new Decoder().decode(detectorRXingResult); - } catch (NotFoundException e) { - notFoundException = e; - } catch (FormatException e) { - formatException = e; - } - if (decoderRXingResult == null) { - try { - AztecDetectorRXingResult detectorRXingResult = detector.detect(true); - points = detectorRXingResult.getPoints(); - decoderRXingResult = new Decoder().decode(detectorRXingResult); - } catch (NotFoundException | FormatException e) { - if (notFoundException != null) { - throw notFoundException; - } - if (formatException != null) { - throw formatException; - } - throw e; - } - } - - if (hints != null) { - RXingResultPointCallback rpcb = (RXingResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); - if (rpcb != null) { - for (RXingResultPoint point : points) { - rpcb.foundPossibleRXingResultPoint(point); - } - } - } - - RXingResult result = new RXingResult(decoderRXingResult.getText(), - decoderRXingResult.getRawBytes(), - decoderRXingResult.getNumBits(), - points, - BarcodeFormat.AZTEC, - System.currentTimeMillis()); - - List byteSegments = decoderRXingResult.getByteSegments(); - if (byteSegments != null) { - result.putMetadata(RXingResultMetadataType.BYTE_SEGMENTS, byteSegments); - } - String ecLevel = decoderRXingResult.getECLevel(); - if (ecLevel != null) { - result.putMetadata(RXingResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); - } - result.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]z" + decoderRXingResult.getSymbologyModifier()); - - return result; - } - - @Override - public void reset() { - // do nothing - } - -} diff --git a/src/aztec/AztecWriter.java b/src/aztec/AztecWriter.java deleted file mode 100644 index 01d6135..0000000 --- a/src/aztec/AztecWriter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.Writer; -import com.google.zxing.aztec.encoder.AztecCode; -import com.google.zxing.aztec.encoder.Encoder; -import com.google.zxing.common.BitMatrix; - -import java.nio.charset.Charset; -import java.util.Map; - -/** - * Renders an Aztec code as a {@link BitMatrix}. - */ -public final class AztecWriter implements Writer { - - @Override - public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) { - return encode(contents, format, width, height, null); - } - - @Override - public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map hints) { - Charset charset = null; // Do not add any ECI code by default - int eccPercent = Encoder.DEFAULT_EC_PERCENT; - int layers = Encoder.DEFAULT_AZTEC_LAYERS; - if (hints != null) { - if (hints.containsKey(EncodeHintType.CHARACTER_SET)) { - charset = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString()); - } - if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) { - eccPercent = Integer.parseInt(hints.get(EncodeHintType.ERROR_CORRECTION).toString()); - } - if (hints.containsKey(EncodeHintType.AZTEC_LAYERS)) { - layers = Integer.parseInt(hints.get(EncodeHintType.AZTEC_LAYERS).toString()); - } - } - return encode(contents, format, width, height, charset, eccPercent, layers); - } - - private static BitMatrix encode(String contents, BarcodeFormat format, - int width, int height, - Charset charset, int eccPercent, int layers) { - if (format != BarcodeFormat.AZTEC) { - throw new IllegalArgumentException("Can only encode AZTEC, but got " + format); - } - AztecCode aztec = Encoder.encode(contents, eccPercent, layers, charset); - return renderRXingResult(aztec, width, height); - } - - private static BitMatrix renderRXingResult(AztecCode code, int width, int height) { - BitMatrix input = code.getMatrix(); - if (input == null) { - throw new IllegalStateException(); - } - int inputWidth = input.getWidth(); - int inputHeight = input.getHeight(); - int outputWidth = Math.max(width, inputWidth); - int outputHeight = Math.max(height, inputHeight); - - int multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight); - int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; - int topPadding = (outputHeight - (inputHeight * multiple)) / 2; - - BitMatrix output = new BitMatrix(outputWidth, outputHeight); - - for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { - // Write the contents of this row of the barcode - for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { - if (input.get(inputX, inputY)) { - output.setRegion(outputX, outputY, multiple, multiple); - } - } - } - return output; - } -} diff --git a/src/aztec/DetectorTest.rs b/src/aztec/DetectorTest.rs index b644418..067a93e 100644 --- a/src/aztec/DetectorTest.rs +++ b/src/aztec/DetectorTest.rs @@ -34,7 +34,12 @@ // import java.util.Random; // import java.util.TreeSet; -use crate::common::BitMatrix; +use crate::{aztec::decoder, common::BitMatrix, exceptions::Exceptions}; + +use super::{ + detector::{self, Detector, Point}, + encoder::{self, AztecCode}, +}; /** * Tests for the Detector @@ -42,147 +47,198 @@ use crate::common::BitMatrix; * @author Frank Yellin */ - #[test] - fn testErrorInParameterLocatorZeroZero() { +#[test] +fn testErrorInParameterLocatorZeroZero() { // Layers=1, CodeWords=1. So the parameter info and its Reed-Solomon info // will be completely zero! testErrorInParameterLocator("X"); - } +} - #[test] - fn testErrorInParameterLocatorCompact() { +#[test] +fn testErrorInParameterLocatorCompact() { testErrorInParameterLocator("This is an example Aztec symbol for Wikipedia."); - } +} - #[test] - fn testErrorInParameterLocatorNotCompact() { +#[test] +fn testErrorInParameterLocatorNotCompact() { let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz"; - testErrorInParameterLocator(&format!("{}{}{}",alphabet , alphabet , alphabet)); - } + testErrorInParameterLocator(&format!("{}{}{}", alphabet, alphabet, alphabet)); +} - // Test that we can tolerate errors in the parameter locator bits - fn testErrorInParameterLocator( data:&str) { - let aztec = Encoder.encode(data, 25, Encoder.DEFAULT_AZTEC_LAYERS); - let random = new Random(aztec.getMatrix().hashCode()); // pseudo-random, but deterministic +// Test that we can tolerate errors in the parameter locator bits +fn testErrorInParameterLocator(data: &str) { + let aztec = encoder::encoder::encode(data, 25, encoder::encoder::DEFAULT_AZTEC_LAYERS) + .expect("encode should create"); + let random = rand::thread_rng(); //Random(aztec.getMatrix().hashCode()); // pseudo-random, but deterministic let layers = aztec.getLayers(); let compact = aztec.isCompact(); - let 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()); + let orientationPoints = getOrientationPoints(&aztec); + for isMirror in [false, true] { + // for (boolean isMirror : new boolean[] { false, true }) { + for matrix in getRotations(aztec.getMatrix()) { + // for (BitMatrix matrix : getRotations(aztec.getMatrix())) { + // Systematically try every possible 1- and 2-bit error. + for error1 in 0..orientationPoints.size() { + // for (int error1 = 0; error1 < orientationPoints.size(); error1++) { + for error2 in error1..orientationPoints.size() { + // for (int error2 = error1; error2 < orientationPoints.size(); error2++) { + let copy = if isMirror { + transpose(&matrix) + } else { + 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. + let r = Detector::new(makeLarger(©, 3)).detect(isMirror); + assert!(r.is_ok()); + let r = r.expect("result already tested as ok"); + assert_eq!(r.getNbLayers(), layers); + assert_eq!(r.isCompact(), compact); + let res = decoder::decode(&r).expect("decode should be ok"); + assert_eq!(data, res.getText()); + } + } + // Try a few random three-bit errors; + for i in 0..5 { + // for (int i = 0; i < 5; i++) { + let copy = clone(&matrix); + let errors = Vec::new(); + while errors.size() < 3 { + // Quick and dirty way of getting three distinct integers between 1 and n. + errors.push(random.nextInt(orientationPoints.size())); + } + for error in errors { + // for (int error : errors) { + copy.flip( + orientationPoints.get(error).getX(), + orientationPoints.get(error).getY(), + ); + } + // try { + if let Err(res) = detector::Detector::new(makeLarger(©, 3)).detect(false) { + if let Exceptions::NotFoundException(msg) = res { + // all ok + } else { + panic!("Should not reach here"); + } + } else { + panic!("Should not reach here"); + } + // // new Detector(makeLarger(copy, 3)).detect(false); + // fail("Should not reach here"); + // } catch (NotFoundException expected) { + // // continue + // } } - // 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 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 - fn makeLarger( input:&BitMatrix, factor:u32) -> BitMatrix{ +// Zooms a bit matrix so that each bit is factor x factor +fn makeLarger(input: &BitMatrix, factor: u32) -> BitMatrix { let width = input.getWidth(); - let output = BitMatrix::new(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); + let output = BitMatrix::with_single_dimension(width * factor); + for inputY in 0..width { + // for (int inputY = 0; inputY < width; inputY++) { + for inputX in 0..width { + // 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. - fn getRotations( matrix0:&BitMatrix)-> Vec { +// Returns a list of the four rotations of the BitMatrix. +fn getRotations(matrix0: &BitMatrix) -> Vec { let matrix90 = rotateRight(matrix0); - let matrix180 = rotateRight(matrix90); - let matrix270 = rotateRight(matrix180); - return Arrays.asList(matrix0, matrix90, matrix180, matrix270); - } + let matrix180 = rotateRight(&matrix90); + let matrix270 = rotateRight(&matrix180); + vec![*matrix0, matrix90, matrix180, matrix270] +} - // Rotates a square BitMatrix to the right by 90 degrees - fn rotateRight( input:&BitMatrix) -> BitMatrix{ +// Rotates a square BitMatrix to the right by 90 degrees +fn rotateRight(input: &BitMatrix) -> BitMatrix { let width = input.getWidth(); - let 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); + let result = BitMatrix::with_single_dimension(width); + for x in 0..width { + // for (int x = 0; x < width; x++) { + for y in 0..width { + // 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 - fn transpose( input:&BitMatrix) -> BitMatrix { +// Returns the transpose of a bit matrix, which is equivalent to rotating the +// matrix to the right, and then flipping it left-to-right +fn transpose(input: &BitMatrix) -> BitMatrix { let width = input.getWidth(); - let 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); + let result = BitMatrix::with_single_dimension(width); + for x in 0..width { + // for (int x = 0; x < width; x++) { + for y in 0..width { + // for (int y = 0; y < width; y++) { + if (input.get(x, y)) { + result.set(y, x); + } } - } } return result; - } +} - fn clone( input:&BitMatrix) -> BitMatrix { +fn clone(input: &BitMatrix) -> BitMatrix { let width = input.getWidth(); - let 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); + let result = BitMatrix::with_single_dimension(width); + for x in 0..width { + // for (int x = 0; x < width; x++) { + for y in 0..width { + // for (int y = 0; y < width; y++) { + if input.get(x, y) { + result.set(x, y); + } } - } } - return result; - } + result +} - fn getOrientationPoints( code:&AztecCode) -> Vec { +fn getOrientationPoints(code: &AztecCode) -> Vec { let center = code.getMatrix().getWidth() / 2; - let offset = code.isCompact() ? 5 : 7; - let 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))); - } + let offset = if code.isCompact() { 5 } else { 7 }; + let result = Vec::new(); + let mut xSign = -1; + while xSign <= 1 { + // for (int xSign = -1; xSign <= 1; xSign += 2) { + let mut ySign = -1; + while ySign <= 1 { + // for (int ySign = -1; ySign <= 1; ySign += 2) { + result.add(Point::new(center + xSign * offset, center + ySign * offset)); + result.add(Point::new( + center + xSign * (offset - 1), + center + ySign * offset, + )); + result.add(Point::new( + center + xSign * offset, + center + ySign * (offset - 1), + )); + + ySign += 2; + } + xSign += 2; } return result; - } +} diff --git a/src/aztec/EncoderTest.rs b/src/aztec/EncoderTest.rs index b269af4..75cfc19 100644 --- a/src/aztec/EncoderTest.rs +++ b/src/aztec/EncoderTest.rs @@ -36,7 +36,15 @@ // import java.util.Random; // import java.util.regex.Pattern; -use crate::common::BitArray; +use std::collections::HashMap; + +use encoding::EncodingRef; + +use crate::{common::{BitArray, BitMatrix}, RXingResultPoint, BarcodeFormat, aztec::{encoder::HighLevelEncoder, decoder, shared_test_methods::{toBooleanArray, stripSpace, toBitArray}, AztecDetectorResult::AztecDetectorRXingResult}, EncodeHintType,EncodeHintValue}; + +use super::{AztecWriter, encoder::encoder}; + +use crate::Writer; /** * Aztec 2D generator unit tests. @@ -45,11 +53,11 @@ use crate::common::BitArray; * @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 ISO_8859_1:EncodingRef = encoding::all::ISO_8859_1;//StandardCharsets.ISO_8859_1; + const UTF_8 :EncodingRef= encoding::all::UTF_8; //StandardCharsets.UTF_8; + const SHIFT_JIS:EncodingRef = encoding::label::encoding_from_whatwg_label("Shift_JIS").expect("must exist");//Charset.forName("Shift_JIS"); + const ISO_8859_15:EncodingRef = encoding::all::ISO_8859_15;//Charset.forName("ISO-8859-15"); + const WINDOWS_1252 :EncodingRef= encoding::all::WINDOWS_1252;//Charset.forName("Windows-1252"); const DOTX : &str = "[^.X]"; const SPACES :&str= "\\s+"; @@ -60,98 +68,100 @@ use crate::common::BitArray; #[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"); + 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 +"); } #[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"); + testEncode(r"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, + 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 X X X + X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X X X X + X X X X X X X X X X X X X +"); } 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); + testWriter("Espa\u{00F1}ol", None, 25, true, 1); // Without ECI (implicit ISO-8859-1) + testWriter("Espa\u{00F1}ol", Some(ISO_8859_1), 25, true, 1); // Explicit ISO-8859-1 + testWriter("\u{20AC} 1 sample data.", Some(WINDOWS_1252), 25, true, 2); // ISO-8859-1 can't encode Euro; Windows-1252 can + testWriter("\u{20AC} 1 sample data.", Some(ISO_8859_15), 25, true, 2); + testWriter("\u{20AC} 1 sample data.", Some(UTF_8), 25, true, 2); + testWriter("\u{20AC} 1 sample data.", Some(UTF_8), 100, true, 3); + testWriter("\u{20AC} 1 sample data.", Some(UTF_8), 300, true, 4); + testWriter("\u{20AC} 1 sample data.", Some(UTF_8), 500, false, 5); + testWriter("The capital of Japan is named \u{6771}\u{4EAC}.", Some(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 writer = AztecWriter{}; + let matrix = AztecWriter::encode(data, &BarcodeFormat::AZTEC, 0, 0).expect("matrix must exist"); + let aztec = encoder::encode(data, + encoder::DEFAULT_EC_PERCENT, encoder::DEFAULT_AZTEC_LAYERS).expect("encode should succeed"); let expectedMatrix = aztec.getMatrix(); - assertEquals(matrix, expectedMatrix); + assert_eq!(&matrix, expectedMatrix); } // synthetic tests (encode-decode round-trip) @@ -178,97 +188,97 @@ use crate::common::BitArray; #[test] fn testEncodeDecode5() { - testEncodeDecode("http://test/~!@#*^%&)__ ;:'\"[]{}\\|-+-=`1029384756<>/?abc" - + "Four score and seven our forefathers brought forth", false, 5); + testEncodeDecode(r#"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); + testEncodeDecode(r"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); + 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); + 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"); + testModeMessageComplex(true, 2, 29, ".X .XXX.. ...X XX.. ..X .XX. .XX.X"); + testModeMessageComplex(true, 4, 64, "XX XXXXXX .X.. ...X ..XX .X.. XX.."); + testModeMessageComplex(false, 21, 660, "X.X.. .X.X..X..XX .XXX ..X.. .XXX. .X... ..XXX"); + testModeMessageComplex(false, 32, 4096, "XXXXX XXXXXXXXXXX X.X. ..... XXX.X ..X.. X.XXX"); } #[test] - fn testStuffBits() { + fn testStuffBitsTest() { testStuffBits(5, ".X.X. X.X.X .X.X.", ".X.X. X.X.X .X.X."); testStuffBits(5, ".X.X. ..... .X.X", @@ -308,11 +318,10 @@ use crate::common::BitArray; //'A' 'B' 'C' B/S =1 'd' 'E' 'F' 'G' "...X. ...XX ..X.. XXXXX ....X .XX..X.. ..XX. ..XXX .X..."); - testHighLevelEncodeString( + testHighLevelEncodeStringCount( // 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=", + "09 UAG ^160MEUCIQC0sYS/HpKxnBELR1uB85R20OoqqwFGa0q2uEiYgh6utAIgLl1aBVM4EOTQtMQQYH9M2Z3Dp4qnA/fwWuQ+M8L3V8U=", 823); } @@ -345,62 +354,74 @@ use crate::common::BitArray; // 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))); + for i in 0..3000 { + // for (int i = 0; i <= 3000; i++) { + sb.push(char::from_u32(128 + (i % 30)).unwrap()); } // 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 }) { + for i in [1, 2, 3, 10, 29, 30, 31, 32, 33, + 60, 61, 62, 63, 64, 2076, 2077, 2078, 2079, 2080, 2100]{ + // 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); + let expectedLength = (8 * i) + + if i <= 31 { 10 } else { if i <= 62 {20} else{if i <= 2078 {21} else {31}} }; + // ( (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) { + testHighLevelEncodeStringCount(&sb[..i], expectedLength as u32); + 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); + testHighLevelEncodeStringCount(&format!("a{}" , &sb[.. i - 1]), expectedLength as u32); // A lower case letter at the end will also be merged into binary mode - testHighLevelEncodeString(sb.substring(0, i - 1) + 'a', expectedLength); + testHighLevelEncodeStringCount(&format!("{}a",&sb[..i - 1]), expectedLength as u32); } // A lower case letter at both ends will enough to latch us into LOWER. - testHighLevelEncodeString('a' + sb.substring(0, i) + 'b', expectedLength + 15); + testHighLevelEncodeStringCount(&format!("a{}b",&sb[..i] ), expectedLength as u32+ 15); } - sb = new StringBuilder(); - for (int i = 0; i < 32; i++) { - sb.append('§'); // § forces binary encoding + sb.clear(); + for i in 0..32 { + // for (int i = 0; i < 32; i++) { + sb.push('§'); // § forces binary encoding } - sb.setCharAt(1, 'A'); + sb.replace_range(1..2, "A"); + // sb.setCharAt(1, 'A'); // expect B/S(1) A B/S(30) - testHighLevelEncodeString(sb.toString(), 5 + 20 + 31 * 8); + testHighLevelEncodeStringCount(&sb, 5 + 20 + 31 * 8); - sb = new StringBuilder(); - for (int i = 0; i < 31; i++) { - sb.append('§'); + sb.clear(); + for i in 0..31 { + // for (int i = 0; i < 31; i++) { + sb.push('§'); } - sb.setCharAt(1, 'A'); + sb.replace_range(1..2, "A"); + // sb.setCharAt(1, 'A'); // expect B/S(31) - testHighLevelEncodeString(sb.toString(), 10 + 31 * 8); + testHighLevelEncodeStringCount(&sb, 10 + 31 * 8); - sb = new StringBuilder(); - for (int i = 0; i < 34; i++) { - sb.append('§'); + sb.clear(); + for i in 0..34 { + // for (int i = 0; i < 34; i++) { + sb.push('§'); } - sb.setCharAt(1, 'A'); + sb.replace_range(1..2, "A"); + // sb.setCharAt(1, 'A'); // expect B/S(31) B/S(3) - testHighLevelEncodeString(sb.toString(), 20 + 34 * 8); + testHighLevelEncodeStringCount(&sb, 20 + 34 * 8); - sb = new StringBuilder(); - for (int i = 0; i < 64; i++) { - sb.append('§'); + sb.clear(); + for i in 0..64 { + // for (int i = 0; i < 64; i++) { + sb.push('§'); } - sb.setCharAt(30, 'A'); + sb.replace_range(30..31, "A"); + // sb.setCharAt(30, 'A'); // expect B/S(64) - testHighLevelEncodeString(sb.toString(), 21 + 64 * 8); + testHighLevelEncodeStringCount(&sb, 21 + 64 * 8); } #[test] @@ -420,7 +441,7 @@ use crate::common::BitArray; // '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", + testHighLevelEncodeString("A\u{200}. \u{200}", // 'A' B/S =2 \200 "." " " \200 "...X. XXXXX ..X.. X....... ..X.XXX. ..X..... X......."); } @@ -434,20 +455,20 @@ use crate::common::BitArray; #[test] #[should_panic] fn testUserSpecifiedLayers2() { - doTestUserSpecifiedLayers(-1); + doTestUserSpecifiedLayers(1); } fn doTestUserSpecifiedLayers( userSpecifiedLayers:usize) { - String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - AztecCode aztec = Encoder.encode(alphabet, 25, -2); - assertEquals(2, aztec.getLayers()); - assertTrue(aztec.isCompact()); + let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let aztec = encoder::encode(alphabet, 25, 2).expect("should encode"); + assert_eq!(2, aztec.getLayers()); + assert!(aztec.isCompact()); - aztec = Encoder.encode(alphabet, 25, 32); - assertEquals(32, aztec.getLayers()); - assertFalse(aztec.isCompact()); + aztec = encoder::encode(alphabet, 25, 32).expect("should encode"); + assert_eq!(32, aztec.getLayers()); + assert!(!aztec.isCompact()); - Encoder.encode(alphabet, 25, userSpecifiedLayers); + encoder::encode(alphabet, 25, userSpecifiedLayers as u32); } #[test] @@ -455,132 +476,139 @@ use crate::common::BitArray; 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"; + let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // encodes as 26 * 5 * 4 = 520 bits of data - String alphabet4 = alphabet + alphabet + alphabet + alphabet; - Encoder.encode(alphabet4, 0, -4); + let alphabet4 = format!("{}{}{}{}",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"; + let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // encodes as 26 * 5 * 4 = 520 bits of data - String alphabet4 = alphabet + alphabet + alphabet + alphabet; + let alphabet4 = format!("{}{}{}{}",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()); + let aztecCode = encoder::encode(&alphabet4, 0, encoder::DEFAULT_AZTEC_LAYERS).expect("Should encode"); + assert!(!aztecCode.isCompact()); + assert_eq!(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()); + aztecCode = encoder::encode(&alphabet4[..100], 10, encoder::DEFAULT_AZTEC_LAYERS).expect("should encode"); + assert!(aztecCode.isCompact()); + assert_eq!(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 testEncode( data:&str, compact:bool, layers:u32, expected:&str) { + let aztec = encoder::encode(data, 33, encoder::DEFAULT_AZTEC_LAYERS).expect("should encode"); + assert_eq!( compact, aztec.isCompact(),"Unexpected symbol format (compact)"); + assert_eq!( layers, aztec.getLayers(),"Unexpected nr. of layers"); + let matrix = aztec.getMatrix(); + assert_eq!( expected, matrix.to_string(),"encode() failed"); + let z: BitMatrix; } - 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()); + fn testEncodeDecode( data:&str, compact:bool, layers:u32) { + let aztec = encoder::encode(data, 25, encoder::DEFAULT_AZTEC_LAYERS).expect("should encode"); + assert_eq!( compact, aztec.isCompact(),"Unexpected symbol format (compact)"); + assert_eq!( layers, aztec.getLayers(),"Unexpected nr. of layers"); + let mut matrix = aztec.getMatrix(); + let r = + AztecDetectorRXingResult::new(matrix.clone(), NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); + let res = decoder::decode(&r).expect("decode ok"); + assert_eq!(data, res.getText()); // Check error correction by introducing a few minor errors - Random random = getPseudoRandom(); + let 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()); + r = AztecDetectorRXingResult::new(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); + res = decoder::decode(r); + assert_eq!(data, res.getText()); } fn testWriter( data:&str, - charset:&'static dyn encoding::Encoding, + charset:Option, eccPercent:u32, compact:bool, - layers:usize) throws FormatException { + layers:u32) { // Perform an encode-decode round-trip because it can be lossy. - Map hints = new EnumMap<>(EncodeHintType.class); - if (null != charset) { - hints.put(EncodeHintType.CHARACTER_SET, charset.name()); + let hints = HashMap::new(); + if charset.is_some() { + hints.insert(EncodingHintType::CHARACTER_SET, EncodingHintValue::CharacterSet(charset.unwrap().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()); + // if (null != charset) { + // hints.put(EncodeHintType.CHARACTER_SET, charset.name()); + // } + hints.insert(EncodeHintType::ERROR_CORRECTION, eccPercent); + let writer = AztecWriter{}; + let matrix = AztecWriter::encode_with_hints(data, &BarcodeFormat::AZTEC, 0, 0, &hints).expect("encoder created"); + let aztec = encoder::encode_with_charset(data, eccPercent, + encoder::DEFAULT_AZTEC_LAYERS, charset.unwrap()).expect("encode should encode"); + assert_eq!( compact, aztec.isCompact(),"Unexpected symbol format (compact)"); + assert_eq!( layers, aztec.getLayers(),"Unexpected nr. of layers"); + let matrix2 = aztec.getMatrix(); + assert_eq!(&matrix, matrix2); + let r = + AztecDetectorRXingResult::new(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); + let res = decoder::decode(&r); + assert_eq!(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++) { + let ecWords = aztec.getCodeWords() * eccPercent / 100 / 2; + let random = getPseudoRandom(); + for i in 0 ..ecWords { + // 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); + let x = if random.nextBoolean() + {random.nextInt(aztec.getLayers() * 2)} + else {matrix.getWidth() - 1 - random.nextInt(aztec.getLayers() * 2)}; + let y = if random.nextBoolean() + {random.nextInt(aztec.getLayers() * 2)} + else {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()); + r = AztecDetectorRXingResult::nwq(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers()); + res = decoder::decode(r); + assert_eq!(data, res.getText()); } - fn getPseudoRandom() -> rand::Rng{ - return new Random(0xDEADBEEF); + fn getPseudoRandom() -> rand::rngs::ThreadRng{ + rand::thread_rng() } - 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 testModeMessageComplex( compact:bool, layers:u32, words:usize, expected:&str) { + let indata = encoder::generateModeMessage(compact, layers, words); + assert_eq!( stripSpace(expected), stripSpace(indata.toString()),"generateModeMessage() failed"); } 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())); + let indata = toBitArray(bits); + let stuffed = encoder::stuffBits(&indata, wordSize); + assert_eq!( + stripSpace(expected), stripSpace(stuffed.toString()), + "stuffBits() failed for input string: {}" , bits); } 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))); + let bits = HighLevelEncoder::new(s.getBytes(StandardCharsets.ISO_8859_1)).encode(); + let receivedBits = stripSpace(bits.toString()); + assert_eq!( stripSpace(expectedBits), receivedBits,"highLevelEncode() failed for input string: {}" , s); + assert_eq!(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))); + fn testHighLevelEncodeStringCount( s:&str, expectedReceivedBits:u32) { + let bits = HighLevelEncoder::new(s.getBytes(StandardCharsets.ISO_8859_1)).encode().unwrap(); + let receivedBitCount = stripSpace(bits.toString()).len(); + assert_eq!( + expectedReceivedBits, receivedBitCount, + "highLevelEncode() failed for input string: {}" , s); + assert_eq!(s, decoder::highLevelDecode(&toBooleanArray(&bits))); } diff --git a/src/aztec/aztec_reader.rs b/src/aztec/aztec_reader.rs new file mode 100644 index 0000000..8ace9b5 --- /dev/null +++ b/src/aztec/aztec_reader.rs @@ -0,0 +1,123 @@ +/* + * 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. + */ + +use std::{collections::HashMap, time::UNIX_EPOCH}; + +use crate::{ + common::{DecoderRXingResult, DetectorRXingResult}, + exceptions::Exceptions, + BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, RXingResult, + RXingResultMetadataType, Reader, RXingResultMetadataValue, +}; + +use super::{decoder, detector::Detector}; + +/** + * This implementation can detect and decode Aztec codes in an image. + * + * @author David Olivier + */ +pub struct AztecReader; + +impl Reader for AztecReader { + /** + * Locates and decodes a Data Matrix code in an image. + * + * @return a String representing the content encoded by the Data Matrix code + * @throws NotFoundException if a Data Matrix code cannot be found + * @throws FormatException if a Data Matrix code cannot be decoded + */ + fn decode(image: &BinaryBitmap) -> Result { + Self::decode_with_hints(image, &HashMap::new()) + } + + fn decode_with_hints( + image: &BinaryBitmap, + hints: &HashMap, + ) -> Result { + // let notFoundException = None; + // let formatException = None; + let mut detector = Detector::new(image.getBlackMatrix()?.clone()); + let mut points; + let mut decoderRXingResult: DecoderRXingResult; + // try { + let detectorRXingResult = detector.detect(false)?; + + points = detectorRXingResult.getPoints(); + decoderRXingResult = decoder::decode(&detectorRXingResult)?; + // } catch (NotFoundException e) { + // notFoundException = e; + // } catch (FormatException e) { + // formatException = e; + // } + // if (decoderRXingResult == null) { + // try { + let detectorRXingResult = detector.detect(true)?; + points = detectorRXingResult.getPoints(); + decoderRXingResult = decoder::decode(&detectorRXingResult)?; + // } catch (NotFoundException | FormatException e) { + // if (notFoundException != null) { + // throw notFoundException; + // } + // if (formatException != null) { + // throw formatException; + // } + // throw e; + // } + // } + + if let Some(rpcb) = hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK) { + if let DecodeHintValue::NeedResultPointCallback(cb) = rpcb { + for point in points { + cb(point); + } + } + } + + let mut result = RXingResult::new_complex( + decoderRXingResult.getText(), + decoderRXingResult.getRawBytes().clone(), + decoderRXingResult.getNumBits(), + points.to_vec(), + BarcodeFormat::AZTEC, + std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_millis(), + ); + + + + let byteSegments = decoderRXingResult.getByteSegments(); + if !byteSegments.is_empty() { + result.putMetadata(RXingResultMetadataType::BYTE_SEGMENTS, RXingResultMetadataValue::ByteSegments(byteSegments.clone())); + } + let ecLevel = decoderRXingResult.getECLevel(); + if !ecLevel.is_empty() { + result.putMetadata(RXingResultMetadataType::ERROR_CORRECTION_LEVEL, RXingResultMetadataValue::ErrorCorrectionLevel(ecLevel.to_owned())); + } + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier(format!("]z{}", decoderRXingResult.getSymbologyModifier())), + ); + + Ok(result) + } + + fn reset() { + // do nothing + } +} diff --git a/src/aztec/aztec_writer.rs b/src/aztec/aztec_writer.rs new file mode 100644 index 0000000..fd00849 --- /dev/null +++ b/src/aztec/aztec_writer.rs @@ -0,0 +1,157 @@ +/* + * 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. + */ + +use std::collections::HashMap; + +use encoding::EncodingRef; + +use crate::{ + common::BitMatrix, exceptions::Exceptions, BarcodeFormat, EncodeHintType, EncodeHintValue, + Writer, +}; + +use super::encoder::{encoder, AztecCode}; + +/** + * Renders an Aztec code as a {@link BitMatrix}. + */ +pub struct AztecWriter; + +impl Writer for AztecWriter { + fn encode( + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + ) -> Result { + Self::encode_with_hints(contents, format, width, height, &HashMap::new()) + } + + fn encode_with_hints( + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + hints: &std::collections::HashMap, + ) -> Result { + let mut charset = None; // Do not add any ECI code by default + let mut eccPercent = encoder::DEFAULT_EC_PERCENT; + let mut layers = encoder::DEFAULT_AZTEC_LAYERS; + if hints.contains_key(&EncodeHintType::CHARACTER_SET) { + if let EncodeHintValue::CharacterSet(cset_name) = hints + .get(&EncodeHintType::CHARACTER_SET) + .expect("already knonw presence") + { + charset = Some(encoding::label::encoding_from_whatwg_label(cset_name).unwrap()); + } + // charset = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString()); + } + if hints.contains_key(&EncodeHintType::ERROR_CORRECTION) { + if let EncodeHintValue::ErrorCorrection(ecc_level) = hints + .get(&EncodeHintType::ERROR_CORRECTION) + .expect("key exists") + { + eccPercent = ecc_level.parse().expect("should convert to int"); + } + // eccPercent = Integer.parseInt(hints.get(EncodeHintType::ERROR_CORRECTION).toString()); + } + if hints.contains_key(&EncodeHintType::AZTEC_LAYERS) { + if let EncodeHintValue::AztecLayers(az_layers) = hints + .get(&EncodeHintType::AZTEC_LAYERS) + .expect("key exists") + { + layers = *az_layers; + } + // layers = Integer.parseInt(hints.get(EncodeHintType.AZTEC_LAYERS).toString()); + } + encode( + contents, + *format, + width as u32, + height as u32, + charset, + eccPercent, + layers, + ) + } +} + +fn encode( + contents: &str, + format: BarcodeFormat, + width: u32, + height: u32, + charset: Option, + eccPercent: u32, + layers: u32, +) -> Result { + if format != BarcodeFormat::AZTEC { + return Err(Exceptions::IllegalArgumentException(format!( + "Can only encode AZTEC, but got {:?}", + format + ))); + } + let aztec = if let Some(cset) = charset { + encoder::encode_with_charset(contents, eccPercent, layers, cset)? + } else { + encoder::encode(contents, eccPercent, layers)? + }; + renderRXingResult(&aztec, width, height) +} + +fn renderRXingResult(code: &AztecCode, width: u32, height: u32) -> Result { + let input = code.getMatrix(); + // if input == null { + // throw new IllegalStateException(); + // } + let inputWidth = input.getWidth(); + let inputHeight = input.getHeight(); + let outputWidth = width.max(inputWidth); + let outputHeight = height.max(inputHeight); + + let multiple = (outputWidth / inputWidth).min(outputHeight / inputHeight); + let leftPadding = (outputWidth - (inputWidth * multiple)) / 2; + let topPadding = (outputHeight - (inputHeight * multiple)) / 2; + + let mut output = BitMatrix::new(outputWidth, outputHeight)?; + + let mut inputY = 0; + let mut outputY = topPadding; + while inputY < inputHeight { + let mut inputX = 0; + let mut outputX = leftPadding; + while inputX < inputWidth { + if input.get(inputX, inputY) { + output.setRegion(outputX, outputY, multiple, multiple); + } + + inputX += 1; + outputX += multiple; + } + + inputY += 1; + outputY += multiple + } + // for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { + // // Write the contents of this row of the barcode + // for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { + // if (input.get(inputX, inputY)) { + // output.setRegion(outputX, outputY, multiple, multiple); + // } + // } + // } + Ok(output) +} diff --git a/src/aztec/detector.rs b/src/aztec/detector.rs index 5de50d6..352f417 100644 --- a/src/aztec/detector.rs +++ b/src/aztec/detector.rs @@ -735,7 +735,7 @@ impl Detector { } #[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct Point { +pub struct Point { x: i32, y: i32, } diff --git a/src/aztec/encoder/Encoder.java b/src/aztec/encoder/Encoder.java deleted file mode 100644 index 4acd8f6..0000000 --- a/src/aztec/encoder/Encoder.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * 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.common.BitArray; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.reedsolomon.GenericGF; -import com.google.zxing.common.reedsolomon.ReedSolomonEncoder; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -/** - * Generates Aztec 2D barcodes. - * - * @author Rustam Abdullaev - */ -public final class Encoder { - - public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words - public static final int DEFAULT_AZTEC_LAYERS = 0; - private static final int MAX_NB_BITS = 32; - private static final int MAX_NB_BITS_COMPACT = 4; - - private static final int[] WORD_SIZE = { - 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 - }; - - private Encoder() { - } - - /** - * Encodes the given string content as an Aztec symbol (without ECI code) - * - * @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1) - * @return Aztec symbol matrix with metadata - */ - public static AztecCode encode(String data) { - return encode(data.getBytes(StandardCharsets.ISO_8859_1)); - } - - /** - * Encodes the given string content as an Aztec symbol (without ECI code) - * - * @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1) - * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, - * a minimum of 23% + 3 words is recommended) - * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers - * @return Aztec symbol matrix with metadata - */ - public static AztecCode encode(String data, int minECCPercent, int userSpecifiedLayers) { - return encode(data.getBytes(StandardCharsets.ISO_8859_1), minECCPercent, userSpecifiedLayers, null); - } - - /** - * Encodes the given string content as an Aztec symbol - * - * @param data input data string - * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, - * a minimum of 23% + 3 words is recommended) - * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers - * @param charset character set in which to encode string using ECI; if null, no ECI code - * will be inserted, and the string must be encodable as ISO/IEC 8859-1 - * (Latin-1), the default encoding of the symbol. - * @return Aztec symbol matrix with metadata - */ - public static AztecCode encode(String data, int minECCPercent, int userSpecifiedLayers, Charset charset) { - byte[] bytes = data.getBytes(null != charset ? charset : StandardCharsets.ISO_8859_1); - return encode(bytes, minECCPercent, userSpecifiedLayers, charset); - } - - /** - * Encodes the given binary content as an Aztec symbol (without ECI code) - * - * @param data input data string - * @return Aztec symbol matrix with metadata - */ - public static AztecCode encode(byte[] data) { - return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS, null); - } - - /** - * Encodes the given binary content as an Aztec symbol (without ECI code) - * - * @param data input data string - * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, - * a minimum of 23% + 3 words is recommended) - * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers - * @return Aztec symbol matrix with metadata - */ - public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) { - return encode(data, minECCPercent, userSpecifiedLayers, null); - } - - /** - * Encodes the given binary content as an Aztec symbol - * - * @param data input data string - * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, - * a minimum of 23% + 3 words is recommended) - * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers - * @param charset character set to mark using ECI; if null, no ECI code will be inserted, and the - * default encoding of ISO/IEC 8859-1 will be assuming by readers. - * @return Aztec symbol matrix with metadata - */ - public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers, Charset charset) { - // High-level encode - BitArray bits = new HighLevelEncoder(data, charset).encode(); - - // stuff bits and choose symbol size - int eccBits = bits.getSize() * minECCPercent / 100 + 11; - int totalSizeBits = bits.getSize() + eccBits; - boolean compact; - int layers; - int totalBitsInLayer; - int wordSize; - BitArray stuffedBits; - if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) { - compact = userSpecifiedLayers < 0; - layers = Math.abs(userSpecifiedLayers); - if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) { - throw new IllegalArgumentException( - String.format("Illegal value %s for layers", userSpecifiedLayers)); - } - totalBitsInLayer = totalBitsInLayer(layers, compact); - wordSize = WORD_SIZE[layers]; - int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize); - stuffedBits = stuffBits(bits, wordSize); - if (stuffedBits.getSize() + eccBits > usableBitsInLayers) { - throw new IllegalArgumentException("Data to large for user specified layer"); - } - if (compact && stuffedBits.getSize() > wordSize * 64) { - // Compact format only allows 64 data words, though C4 can hold more words than that - throw new IllegalArgumentException("Data to large for user specified layer"); - } - } else { - wordSize = 0; - stuffedBits = null; - // 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 (int i = 0; ; i++) { - if (i > MAX_NB_BITS) { - throw new IllegalArgumentException("Data too large for an Aztec code"); - } - compact = i <= 3; - layers = compact ? i + 1 : i; - totalBitsInLayer = totalBitsInLayer(layers, compact); - if (totalSizeBits > totalBitsInLayer) { - continue; - } - // [Re]stuff the bits if this is the first opportunity, or if the - // wordSize has changed - if (stuffedBits == null || wordSize != WORD_SIZE[layers]) { - wordSize = WORD_SIZE[layers]; - stuffedBits = stuffBits(bits, wordSize); - } - int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize); - if (compact && stuffedBits.getSize() > wordSize * 64) { - // Compact format only allows 64 data words, though C4 can hold more words than that - continue; - } - if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) { - break; - } - } - } - BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize); - - // generate mode message - int messageSizeInWords = stuffedBits.getSize() / wordSize; - BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords); - - // allocate symbol - int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines - int[] alignmentMap = new int[baseMatrixSize]; - int matrixSize; - if (compact) { - // no alignment marks in compact mode, alignmentMap is a no-op - matrixSize = baseMatrixSize; - for (int i = 0; i < alignmentMap.length; i++) { - alignmentMap[i] = i; - } - } else { - 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; - } - } - BitMatrix matrix = new BitMatrix(matrixSize); - - // draw data bits - for (int i = 0, rowOffset = 0; i < layers; i++) { - int rowSize = (layers - i) * 4 + (compact ? 9 : 12); - for (int j = 0; j < rowSize; j++) { - int columnOffset = j * 2; - for (int k = 0; k < 2; k++) { - if (messageBits.get(rowOffset + columnOffset + k)) { - matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]); - } - if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) { - matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]); - } - if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) { - matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]); - } - if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) { - matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]); - } - } - } - rowOffset += rowSize * 8; - } - - // draw mode message - drawModeMessage(matrix, compact, matrixSize, modeMessage); - - // draw alignment marks - if (compact) { - drawBullsEye(matrix, matrixSize / 2, 5); - } else { - drawBullsEye(matrix, matrixSize / 2, 7); - for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) { - for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) { - matrix.set(matrixSize / 2 - j, k); - matrix.set(matrixSize / 2 + j, k); - matrix.set(k, matrixSize / 2 - j); - matrix.set(k, matrixSize / 2 + j); - } - } - } - - AztecCode aztec = new AztecCode(); - aztec.setCompact(compact); - aztec.setSize(matrixSize); - aztec.setLayers(layers); - aztec.setCodeWords(messageSizeInWords); - aztec.setMatrix(matrix); - return aztec; - } - - private static void drawBullsEye(BitMatrix matrix, int center, int size) { - for (int i = 0; i < size; i += 2) { - for (int j = center - i; j <= center + i; j++) { - matrix.set(j, center - i); - matrix.set(j, center + i); - matrix.set(center - i, j); - matrix.set(center + i, j); - } - } - 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); - } - - static BitArray generateModeMessage(boolean compact, int layers, int messageSizeInWords) { - BitArray modeMessage = new BitArray(); - if (compact) { - modeMessage.appendBits(layers - 1, 2); - modeMessage.appendBits(messageSizeInWords - 1, 6); - modeMessage = generateCheckWords(modeMessage, 28, 4); - } else { - modeMessage.appendBits(layers - 1, 5); - modeMessage.appendBits(messageSizeInWords - 1, 11); - modeMessage = generateCheckWords(modeMessage, 40, 4); - } - return modeMessage; - } - - private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) { - int center = matrixSize / 2; - if (compact) { - for (int i = 0; i < 7; i++) { - int offset = center - 3 + i; - 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 (int i = 0; i < 10; i++) { - int offset = center - 5 + i + i / 5; - 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); - } - } - } - } - - private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) { - // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed - int messageSizeInWords = bitArray.getSize() / wordSize; - ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize)); - int totalWords = totalBits / wordSize; - int[] messageWords = bitsToWords(bitArray, wordSize, totalWords); - rs.encode(messageWords, totalWords - messageSizeInWords); - int startPad = totalBits % wordSize; - BitArray messageBits = new BitArray(); - messageBits.appendBits(0, startPad); - for (int messageWord : messageWords) { - messageBits.appendBits(messageWord, wordSize); - } - return messageBits; - } - - private static int[] bitsToWords(BitArray stuffedBits, int wordSize, int totalWords) { - int[] message = new int[totalWords]; - int i; - int n; - for (i = 0, n = stuffedBits.getSize() / wordSize; i < n; i++) { - int value = 0; - for (int j = 0; j < wordSize; j++) { - value |= stuffedBits.get(i * wordSize + j) ? (1 << wordSize - j - 1) : 0; - } - message[i] = value; - } - return message; - } - - private static GenericGF getGF(int wordSize) { - switch (wordSize) { - case 4: - return GenericGF.AZTEC_PARAM; - case 6: - return GenericGF.AZTEC_DATA_6; - case 8: - return GenericGF.AZTEC_DATA_8; - case 10: - return GenericGF.AZTEC_DATA_10; - case 12: - return GenericGF.AZTEC_DATA_12; - default: - throw new IllegalArgumentException("Unsupported word size " + wordSize); - } - } - - static BitArray stuffBits(BitArray bits, int wordSize) { - BitArray out = new BitArray(); - - int n = bits.getSize(); - int mask = (1 << wordSize) - 2; - for (int i = 0; i < n; i += wordSize) { - int word = 0; - for (int j = 0; j < wordSize; j++) { - if (i + j >= n || bits.get(i + j)) { - word |= 1 << (wordSize - 1 - j); - } - } - if ((word & mask) == mask) { - out.appendBits(word & mask, wordSize); - i--; - } else if ((word & mask) == 0) { - out.appendBits(word | 1, wordSize); - i--; - } else { - out.appendBits(word, wordSize); - } - } - return out; - } - - private static int totalBitsInLayer(int layers, boolean compact) { - return ((compact ? 88 : 112) + 16 * layers) * layers; - } -} diff --git a/src/aztec/encoder/aztec_code.rs b/src/aztec/encoder/aztec_code.rs index 55db13b..0c99f22 100644 --- a/src/aztec/encoder/aztec_code.rs +++ b/src/aztec/encoder/aztec_code.rs @@ -30,6 +30,16 @@ pub struct AztecCode { } impl AztecCode { + pub fn new(compact: bool, size: u32, layers: u32, code_words: u32, matrix: BitMatrix) -> Self { + Self { + compact, + size, + layers, + code_words, + matrix, + } + } + /** * @return {@code true} if compact instead of full mode */ diff --git a/src/aztec/encoder/binary_shift_token.rs b/src/aztec/encoder/binary_shift_token.rs index ba2ed5e..b6bb26b 100644 --- a/src/aztec/encoder/binary_shift_token.rs +++ b/src/aztec/encoder/binary_shift_token.rs @@ -18,7 +18,7 @@ use std::fmt; use crate::common::BitArray; -#[derive(Debug,PartialEq, Eq,Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct BinaryShiftToken { binaryShiftStart: u32, binaryShiftByteCount: u32, @@ -44,7 +44,7 @@ impl BinaryShiftToken { bitArray.appendBits(bsbc as u32 - 31, 16); } else if (i == 0) { // 1 <= binaryShiftByteCode <= 62 - bitArray.appendBits(bsbc.min( 31) as u32, 5); + bitArray.appendBits(bsbc.min(31) as u32, 5); } else { // 32 <= binaryShiftCount <= 62 and i == 31 bitArray.appendBits(bsbc as u32 - 31, 5); diff --git a/src/aztec/encoder/encoder.rs b/src/aztec/encoder/encoder.rs new file mode 100644 index 0000000..00d8182 --- /dev/null +++ b/src/aztec/encoder/encoder.rs @@ -0,0 +1,523 @@ +/* + * 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. + */ + +use encoding::Encoding; + +use crate::{ + common::{ + reedsolomon::{ + get_predefined_genericgf, GenericGF, PredefinedGenericGF, ReedSolomonEncoder, + }, + BitArray, BitMatrix, + }, + exceptions::Exceptions, +}; + +use super::{AztecCode, HighLevelEncoder}; + +/** + * Generates Aztec 2D barcodes. + * + * @author Rustam Abdullaev + */ + +pub const DEFAULT_EC_PERCENT: u32 = 33; // default minimal percentage of error check words +pub const DEFAULT_AZTEC_LAYERS: u32 = 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, +]; + +/** + * Encodes the given string content as an Aztec symbol (without ECI code) + * + * @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1) + * @return Aztec symbol matrix with metadata + */ +pub fn encode_simple(data: &str) -> Result { + let bytes = encoding::all::ISO_8859_1 + .encode(data, encoding::EncoderTrap::Replace) + .unwrap(); + encode_bytes_simple(&bytes) +} + +/** + * Encodes the given string content as an Aztec symbol (without ECI code) + * + * @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1) + * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, + * a minimum of 23% + 3 words is recommended) + * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers + * @return Aztec symbol matrix with metadata + */ +pub fn encode( + data: &str, + minECCPercent: u32, + userSpecifiedLayers: u32, +) -> Result { + let bytes = encoding::all::ISO_8859_1 + .encode(data, encoding::EncoderTrap::Replace) + .unwrap(); + encode_bytes(&bytes, minECCPercent, userSpecifiedLayers) +} + +/** + * Encodes the given string content as an Aztec symbol + * + * @param data input data string + * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, + * a minimum of 23% + 3 words is recommended) + * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers + * @param charset character set in which to encode string using ECI; if null, no ECI code + * will be inserted, and the string must be encodable as ISO/IEC 8859-1 + * (Latin-1), the default encoding of the symbol. + * @return Aztec symbol matrix with metadata + */ +pub fn encode_with_charset( + data: &str, + minECCPercent: u32, + userSpecifiedLayers: u32, + charset: &'static dyn encoding::Encoding, +) -> Result { + let bytes = charset + .encode(data, encoding::EncoderTrap::Replace) + .unwrap(); //data.getBytes(null != charset ? charset : StandardCharsets.ISO_8859_1); + encode_bytes_with_charset(&bytes, minECCPercent, userSpecifiedLayers, charset) +} + +/** + * Encodes the given binary content as an Aztec symbol (without ECI code) + * + * @param data input data string + * @return Aztec symbol matrix with metadata + */ +pub fn encode_bytes_simple(data: &[u8]) -> Result { + encode_bytes(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS) +} + +/** + * Encodes the given binary content as an Aztec symbol (without ECI code) + * + * @param data input data string + * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, + * a minimum of 23% + 3 words is recommended) + * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers + * @return Aztec symbol matrix with metadata + */ +pub fn encode_bytes( + data: &[u8], + minECCPercent: u32, + userSpecifiedLayers: u32, +) -> Result { + encode_bytes_with_charset( + data, + minECCPercent, + userSpecifiedLayers, + encoding::all::ISO_8859_1, + ) +} + +/** + * Encodes the given binary content as an Aztec symbol + * + * @param data input data string + * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008, + * a minimum of 23% + 3 words is recommended) + * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers + * @param charset character set to mark using ECI; if null, no ECI code will be inserted, and the + * default encoding of ISO/IEC 8859-1 will be assuming by readers. + * @return Aztec symbol matrix with metadata + */ +pub fn encode_bytes_with_charset( + data: &[u8], + minECCPercent: u32, + userSpecifiedLayers: u32, + charset: &'static dyn encoding::Encoding, +) -> Result { + // High-level encode + let bits = HighLevelEncoder::with_charset(data.into(), charset).encode()?; + + // stuff bits and choose symbol size + let eccBits = bits.getSize() as u32 * minECCPercent / 100 + 11; + let totalSizeBits = bits.getSize() as u32 + eccBits; + let mut compact; + let mut layers; + let mut totalBitsInLayerVar; + let mut wordSize; + let mut stuffedBits; + if userSpecifiedLayers != DEFAULT_AZTEC_LAYERS { + compact = userSpecifiedLayers < 0; + layers = userSpecifiedLayers; + if layers + > (if compact { + MAX_NB_BITS_COMPACT + } else { + MAX_NB_BITS + }) + { + return Err(Exceptions::IllegalArgumentException(format!( + "Illegal value {} for layers", + userSpecifiedLayers + ))); + } + totalBitsInLayerVar = totalBitsInLayer(layers, compact); + wordSize = WORD_SIZE[layers as usize]; + let usableBitsInLayers = totalBitsInLayerVar - (totalBitsInLayerVar % wordSize); + stuffedBits = stuffBits(&bits, wordSize as usize); + if stuffedBits.getSize() as u32+ eccBits > usableBitsInLayers { + return Err(Exceptions::IllegalArgumentException( + "Data to large for user specified layer".to_owned(), + )); + } + if compact && stuffedBits.getSize() as u32 > wordSize * 64 { + // Compact format only allows 64 data words, though C4 can hold more words than that + return Err(Exceptions::IllegalArgumentException( + "Data to large for user specified layer".to_owned(), + )); + } + } else { + wordSize = 0; + stuffedBits = BitArray::new(); + // 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. + let mut i = 0; + loop { + // for (int i = 0; ; i++) { + if i > MAX_NB_BITS { + return Err(Exceptions::IllegalArgumentException( + "Data too large for an Aztec code".to_owned(), + )); + } + compact = i <= 3; + layers = if compact { i + 1 } else { i }; + totalBitsInLayerVar = totalBitsInLayer(layers, compact); + if totalSizeBits > totalBitsInLayerVar as u32 { + continue; + } + // [Re]stuff the bits if this is the first opportunity, or if the + // wordSize has changed + if stuffedBits.getSize() == 0 || wordSize != WORD_SIZE[layers as usize] { + wordSize = WORD_SIZE[layers as usize]; + stuffedBits = stuffBits(&bits, wordSize as usize); + } + let usableBitsInLayers = totalBitsInLayerVar - (totalBitsInLayerVar % wordSize); + if compact && stuffedBits.getSize() as u32 > wordSize * 64 { + // Compact format only allows 64 data words, though C4 can hold more words than that + continue; + } + if stuffedBits.getSize()as u32 + eccBits<= usableBitsInLayers { + break; + } + i += 1; + } + } + let messageBits = generateCheckWords( + &stuffedBits, + totalBitsInLayerVar as usize, + wordSize as usize, + ); + + // generate mode message + let messageSizeInWords = stuffedBits.getSize() as u32 / wordSize; + let modeMessage = generateModeMessage(compact, layers as u32, messageSizeInWords); + + // allocate symbol + let baseMatrixSize = (if compact { 11 } else { 14 }) + layers * 4; // not including alignment lines + let mut alignmentMap = vec![0u32; baseMatrixSize as usize]; + let matrixSize; + if compact { + // no alignment marks in compact mode, alignmentMap is a no-op + matrixSize = baseMatrixSize; + for i in 0..alignmentMap.len() { + // for (int i = 0; i < alignmentMap.length; i++) { + alignmentMap[i] = i as 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 { + // for (int i = 0; i < origCenter; i++) { + let newOffset = (i + i / 15) as u32; + alignmentMap[origCenter - i - 1] = center as u32 - newOffset - 1; + alignmentMap[origCenter + i] = center as u32 + newOffset + 1; + } + } + let mut matrix = BitMatrix::with_single_dimension(matrixSize as u32); + + // draw data bits + let mut rowOffset = 0; + for i in 0..layers as usize { + // for (int i = 0, rowOffset = 0; i < layers; i++) { + let rowSize = (layers as usize - i) * 4 + (if compact { 9 } else { 12 }); + 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++) { + if messageBits.get(rowOffset + columnOffset + k) { + matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]); + } + if messageBits.get(rowOffset + rowSize * 2 + columnOffset + k) { + matrix.set( + alignmentMap[i * 2 + j], + alignmentMap[baseMatrixSize as usize - 1 - i * 2 - k], + ); + } + if messageBits.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 messageBits.get(rowOffset + rowSize * 6 + columnOffset + k) { + matrix.set( + alignmentMap[baseMatrixSize as usize - 1 - i * 2 - j], + alignmentMap[i * 2 + k], + ); + } + } + } + rowOffset += rowSize * 8; + } + + // draw mode message + drawModeMessage(&mut matrix, compact, matrixSize as u32, modeMessage); + + // draw alignment marks + if compact { + drawBullsEye(&mut matrix, matrixSize as u32 / 2, 5); + } else { + drawBullsEye(&mut matrix, matrixSize as u32 / 2, 7); + let mut i = 0; + let mut j = 0; + while i < baseMatrixSize / 2 - 1 { + let mut k = (matrixSize / 2) & 1; + while k < matrixSize { + // for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) { + matrix.set(matrixSize as u32 / 2 - j, k as u32); + matrix.set(matrixSize as u32 / 2 + j, k as u32); + matrix.set(k as u32, matrixSize as u32 / 2 - j); + matrix.set(k as u32, matrixSize as u32 / 2 + j); + + k += 2; + } + + i += 15; + j += 16; + } + // for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) { + // for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) { + // matrix.set(matrixSize / 2 - j, k); + // matrix.set(matrixSize / 2 + j, k); + // matrix.set(k, matrixSize / 2 - j); + // matrix.set(k, matrixSize / 2 + j); + // } + // } + } + + let aztec = AztecCode::new( + compact, + matrixSize as u32, + layers, + messageSizeInWords as u32, + matrix, + ); + // aztec.setCompact(compact); + // aztec.setSize(matrixSize); + // aztec.setLayers(layers); + // aztec.setCodeWords(messageSizeInWords); + // aztec.setMatrix(matrix); + Ok(aztec) +} + +fn drawBullsEye(matrix: &mut BitMatrix, center: u32, size: u32) { + let mut i = 0; + while i < size { + // for (int i = 0; i < size; i += 2) { + for j in (center - 1)..=(center + 1) { + // for (int j = center - i; j <= center + i; j++) { + 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) -> BitArray { + let mut modeMessage = BitArray::new(); + if compact { + modeMessage.appendBits(layers - 1, 2); + modeMessage.appendBits(messageSizeInWords - 1, 6); + modeMessage = generateCheckWords(&modeMessage, 28, 4); + } else { + modeMessage.appendBits(layers - 1, 5); + modeMessage.appendBits(messageSizeInWords - 1, 11); + modeMessage = generateCheckWords(&modeMessage, 40, 4); + } + return modeMessage; +} + +fn drawModeMessage(matrix: &mut BitMatrix, compact: bool, matrixSize: u32, modeMessage: BitArray) { + let center = matrixSize / 2; + if compact { + for i in 0..7usize { + // for (int i = 0; i < 7; i++) { + 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..10usize { + // for (int i = 0; i < 10; i++) { + 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) -> BitArray { + // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed + let messageSizeInWords = bitArray.getSize() / wordSize; + let mut rs = ReedSolomonEncoder::new(getGF(wordSize).expect("Should never have bad value")); + let totalWords = totalBits / wordSize; + let mut messageWords = bitsToWords(bitArray, wordSize, totalWords); + rs.encode(&mut messageWords, totalWords - messageSizeInWords); + let startPad = totalBits % wordSize; + let mut messageBits = BitArray::new(); + messageBits.appendBits(0, startPad); + for messageWord in messageWords { + // for (int messageWord : messageWords) { + messageBits.appendBits(messageWord as u32, wordSize); + } + return messageBits; +} + +fn bitsToWords(stuffedBits: &BitArray, wordSize: usize, totalWords: usize) -> Vec { + let mut message = vec![0i32; totalWords]; + // let i=0; + // let n; + for i in 0..(stuffedBits.getSize() / wordSize) { + // for (i = 0, n = stuffedBits.getSize() / wordSize; i < n; i++) { + let mut value = 0; + for j in 0..wordSize { + // for (int j = 0; j < wordSize; j++) { + value |= if stuffedBits.get(i * wordSize + j) { + 1 << wordSize - j - 1 + } else { + 0 + }; + } + message[i] = value; + } + return message; +} + +fn getGF(wordSize: usize) -> Result { + 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::IllegalArgumentException(format!( + "Unsupported word size {}", + wordSize + ))), + } + // switch (wordSize) { + // case 4: + // return GenericGF.AZTEC_PARAM; + // case 6: + // return GenericGF.AZTEC_DATA_6; + // case 8: + // return GenericGF.AZTEC_DATA_8; + // case 10: + // return GenericGF.AZTEC_DATA_10; + // case 12: + // return GenericGF.AZTEC_DATA_12; + // default: + // throw new IllegalArgumentException("Unsupported word size " + wordSize); + // } +} + +pub fn stuffBits(bits: &BitArray, wordSize: usize) -> BitArray { + let mut out = BitArray::new(); + + let n = bits.getSize(); + let mask = (1 << wordSize) - 2; + let mut i = 0; + while i < n { + // for (int i = 0; i < n; i += wordSize) { + let mut word = 0; + for j in 0..wordSize { + // for (int j = 0; j < wordSize; j++) { + if i + j >= n || bits.get(i + j) { + word |= 1 << (wordSize - 1 - j); + } + } + if (word & mask) == mask { + out.appendBits(word & mask, wordSize); + i -= 1; + } else if (word & mask) == 0 { + out.appendBits(word | 1, wordSize); + i -= 1; + } else { + out.appendBits(word, wordSize); + } + + i += wordSize; + } + return out; +} + +fn totalBitsInLayer(layers: u32, compact: bool) -> u32 { + ((if compact { 88 } else { 112 }) + 16 * layers) * layers + // return ((compact ? 88 : 112) + 16 * layers) * layers; +} diff --git a/src/aztec/encoder/high_level_encoder.rs b/src/aztec/encoder/high_level_encoder.rs index 8c736f1..daf20ee 100644 --- a/src/aztec/encoder/high_level_encoder.rs +++ b/src/aztec/encoder/high_level_encoder.rs @@ -14,9 +14,14 @@ * limitations under the License. */ -use crate::common::BitArray; +use std::cmp::Ordering; +use crate::{ + common::{BitArray, CharacterSetECI}, + exceptions::Exceptions, +}; +use super::{State, Token}; /** * This produces nearly optimal encodings of text into the first-level of @@ -31,342 +36,425 @@ use crate::common::BitArray; * @author Rustam Abdullaev */ pub struct HighLevelEncoder { - - text:Vec, - charset: &'static dyn encoding::Encoding, - + text: Vec, + charset: &'static dyn encoding::Encoding, } impl HighLevelEncoder { - pub const MODE_NAMES : [&str] = ["UPPER", "LOWER", "DIGIT", "MIXED", "PUNCT"]; + pub const MODE_NAMES: [&'static str; 5] = ["UPPER", "LOWER", "DIGIT", "MIXED", "PUNCT"]; - const MODE_UPPER :u32= 0; // 5 bits - const MODE_LOWER :u32 = 1; // 5 bits - const MODE_DIGIT :u32 = 2; // 4 bits - const MODE_MIXED :u32 = 3; // 5 bits - const MODE_PUNCT :u32 = 4; // 5 bits + pub const MODE_UPPER: usize = 0; // 5 bits + pub const MODE_LOWER: usize = 1; // 5 bits + pub const MODE_DIGIT: usize = 2; // 4 bits + pub const MODE_MIXED: usize = 3; // 5 bits + pub const MODE_PUNCT: usize = 4; // 5 bits - // The Latch Table shows, for each pair of Modes, the optimal method for - // getting from one mode to another. In the worst possible case, this can - // be up to 14 bits. In the best possible case, we are already there! - // The high half-word of each entry gives the number of bits. - // The low half-word of each entry are the actual bits necessary to change - const LATCH_TABLE : [[u32]]= [ - [ - 0, - (5 << 16) + 28, // UPPER -> LOWER - (5 << 16) + 30, // UPPER -> DIGIT - (5 << 16) + 29, // UPPER -> MIXED - (10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT - ], - [ - (9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER - 0, - (5 << 16) + 30, // LOWER -> DIGIT - (5 << 16) + 29, // LOWER -> MIXED - (10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT - ], - [ - (4 << 16) + 14, // DIGIT -> UPPER - (9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER - 0, - (9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED - (14 << 16) + (14 << 10) + (29 << 5) + 30, - ] // DIGIT -> UPPER -> MIXED -> PUNCT - , - [ - (5 << 16) + 29, // MIXED -> UPPER - (5 << 16) + 28, // MIXED -> LOWER - (10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT - 0, - (5 << 16) + 30, // MIXED -> PUNCT - ], - [ - (5 << 16) + 31, // PUNCT -> UPPER - (10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER - (10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT - (10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED - 0, - ], - ]; - - // A reverse mapping from [mode][char] to the encoding for that character - // in that mode. An entry of 0 indicates no mapping exists. - const CHAR_MAP : [[u32]] = { - let char_map = vec![vec![0u32;256];5]; - char_map[Self::MODE_UPPER][' '] = 1; - for (int c = 'A'; c <= 'Z'; c++) { - char_map[Self::MODE_UPPER][c] = c - 'A' + 2; - } - char_map[Self::MODE_LOWER][' '] = 1; - for (int c = 'a'; c <= 'z'; c++) { - char_map[Self::MODE_LOWER][c] = c - 'a' + 2; - } - char_map[Self::MODE_DIGIT][' '] = 1; - for (int c = '0'; c <= '9'; c++) { - char_map[Self::MODE_DIGIT][c] = c - '0' + 2; - } - char_map[Self::MODE_DIGIT][','] = 12; - char_map[Self::MODE_DIGIT]['.'] = 13; - let mixedTable = [ - '\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n', - '\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^', - '_', '`', '|', '~', '\177' + // The Latch Table shows, for each pair of Modes, the optimal method for + // getting from one mode to another. In the worst possible case, this can + // be up to 14 bits. In the best possible case, we are already there! + // The high half-word of each entry gives the number of bits. + // The low half-word of each entry are the actual bits necessary to change + pub const LATCH_TABLE: [[u32; 5]; 5] = [ + [ + 0, + (5 << 16) + 28, // UPPER -> LOWER + (5 << 16) + 30, // UPPER -> DIGIT + (5 << 16) + 29, // UPPER -> MIXED + (10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT + ], + [ + (9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER + 0, + (5 << 16) + 30, // LOWER -> DIGIT + (5 << 16) + 29, // LOWER -> MIXED + (10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT + ], + [ + (4 << 16) + 14, // DIGIT -> UPPER + (9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER + 0, + (9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED + (14 << 16) + (14 << 10) + (29 << 5) + 30, + ], // DIGIT -> UPPER -> MIXED -> PUNCT + [ + (5 << 16) + 29, // MIXED -> UPPER + (5 << 16) + 28, // MIXED -> LOWER + (10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT + 0, + (5 << 16) + 30, // MIXED -> PUNCT + ], + [ + (5 << 16) + 31, // PUNCT -> UPPER + (10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER + (10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT + (10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED + 0, + ], ]; - for (int i = 0; i < mixedTable.length; i++) { - CHAR_MAP[MODE_MIXED][mixedTable[i]] = i; - } - let punctTable = [ - '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'', - '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', - '[', ']', '{', '}' - ]; - for (int i = 0; i < punctTable.length; i++) { - if (punctTable[i] > 0) { - CHAR_MAP[MODE_PUNCT][punctTable[i]] = i; - } - } - }; - // private static final int[][] CHAR_MAP = new int[5][256]; - // static { - // CHAR_MAP[MODE_UPPER][' '] = 1; - // for (int c = 'A'; c <= 'Z'; c++) { - // CHAR_MAP[MODE_UPPER][c] = c - 'A' + 2; - // } - // CHAR_MAP[MODE_LOWER][' '] = 1; - // for (int c = 'a'; c <= 'z'; c++) { - // CHAR_MAP[MODE_LOWER][c] = c - 'a' + 2; - // } - // CHAR_MAP[MODE_DIGIT][' '] = 1; - // for (int c = '0'; c <= '9'; c++) { - // CHAR_MAP[MODE_DIGIT][c] = c - '0' + 2; - // } - // CHAR_MAP[MODE_DIGIT][','] = 12; - // CHAR_MAP[MODE_DIGIT]['.'] = 13; - // int[] mixedTable = { - // '\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n', - // '\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^', - // '_', '`', '|', '~', '\177' - // }; - // for (int i = 0; i < mixedTable.length; i++) { - // CHAR_MAP[MODE_MIXED][mixedTable[i]] = i; - // } - // int[] punctTable = { - // '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'', - // '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', - // '[', ']', '{', '}' - // }; - // for (int i = 0; i < punctTable.length; i++) { - // if (punctTable[i] > 0) { - // CHAR_MAP[MODE_PUNCT][punctTable[i]] = i; - // } - // } - // } - // A map showing the available shift codes. (The shifts to BINARY are not - // shown - const SHIFT_TABLE : [[i32]]= { // mode shift codes, per table - let mut shift_table = [[-1i32;6];6]; - - shift_table[Self::MODE_UPPER][Self::MODE_PUNCT] = 0; - - shift_table[Self::MODE_LOWER][Self::MODE_PUNCT] = 0; - shift_table[Self::MODE_LOWER][Self::MODE_UPPER] = 28; - - shift_table[Self::MODE_MIXED][Self::MODE_PUNCT] = 0; - - shift_table[Self::MODE_DIGIT][Self::MODE_PUNCT] = 0; - shift_table[Self::MODE_DIGIT][Self::MODE_UPPER] = 15; - - shift_table - }; - // const SHIFT_TABLE : [[u32]]= new int[6][6]; // mode shift codes, per table - // static { - // for (int[] table : SHIFT_TABLE) { - // Arrays.fill(table, -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; - // } - - - pub fn new(text:Vec) -> Self{ - Self{ - text, - charset: encoding::all::UTF_8, - } - } - - pub fn with_charset(text:Vec, charset:&'static dyn encoding::Encoding) -> Self{ - Self{ - text, - charset, - } - } - - /** - * @return text represented by this encoder encoded as a {@link BitArray} - */ - pub fn encode(&self)-> BitArray { - State initialState = State.INITIAL_STATE; - if (charset != null) { - CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(charset); - if (null == eci) { - throw new IllegalArgumentException("No ECI code for character set " + charset); - } - initialState = initialState.appendFLGn(eci.getValue()); - } - Collection states = Collections.singletonList(initialState); - for (int index = 0; index < text.length; index++) { - int pairCode; - int nextChar = index + 1 < text.length ? text[index + 1] : 0; - switch (text[index]) { - case '\r': - pairCode = nextChar == '\n' ? 2 : 0; - break; - case '.' : - pairCode = nextChar == ' ' ? 3 : 0; - break; - case ',' : - pairCode = nextChar == ' ' ? 4 : 0; - break; - case ':' : - pairCode = nextChar == ' ' ? 5 : 0; - break; - default: - pairCode = 0; - } - if (pairCode > 0) { - // We have one of the four special PUNCT pairs. Treat them specially. - // Get a new set of states for the two new characters. - states = updateStateListForPair(states, index, pairCode); - index++; - } else { - // Get a new set of states for the new character. - states = updateStateListForChar(states, index); - } - } - // We are left with a set of states. Find the shortest one. - State minState = Collections.min(states, new Comparator() { - @Override - public int compare(State a, State b) { - return a.getBitCount() - b.getBitCount(); - } - }); - // Convert it to a bit array, and return. - return minState.toBitArray(text); - } - - // We update a set of states for a new character by updating each state - // for the new character, merging the results, and then removing the - // non-optimal states. - private Collection updateStateListForChar(Iterable states, int index) { - Collection result = new LinkedList<>(); - for (State state : states) { - updateStateForChar(state, index, result); - } - return simplifyStates(result); - } - - // Return a set of states that represent the possible ways of updating this - // state for the next character. The resulting set of states are added to - // the "result" list. - private void updateStateForChar(State state, int index, Collection result) { - char ch = (char) (text[index] & 0xFF); - boolean charInCurrentTable = CHAR_MAP[state.getMode()][ch] > 0; - State stateNoBinary = null; - for (int mode = 0; mode <= MODE_PUNCT; mode++) { - int charInMode = CHAR_MAP[mode][ch]; - if (charInMode > 0) { - if (stateNoBinary == null) { - // Only create stateNoBinary the first time it's required. - stateNoBinary = state.endBinaryShift(index); + // A reverse mapping from [mode][char] to the encoding for that character + // in that mode. An entry of 0 indicates no mapping exists. + pub const CHAR_MAP: [[u8; 256]; 5] = { + let mut char_map = [[0u8; 256]; 5]; + char_map[Self::MODE_UPPER][b' ' as usize] = 1; + let mut c = b'A'; + while c <= b'Z' { + char_map[Self::MODE_UPPER][c as usize] = c - b'A' + 2; + c += 1; } - // Try generating the character by latching to its mode - if (!charInCurrentTable || mode == state.getMode() || mode == MODE_DIGIT) { - // If the character is in the current table, we don't want to latch to - // any other mode except possibly digit (which uses only 4 bits). Any - // other latch would be equally successful *after* this character, and - // so wouldn't save any bits. - State latchState = stateNoBinary.latchAndAppend(mode, charInMode); - result.add(latchState); + // for (int c = 'A'; c <= 'Z'; c++) { + // char_map[Self::MODE_UPPER][c] = c - 'A' + 2; + // } + char_map[Self::MODE_LOWER][b' ' as usize] = 1; + let mut c = b'a'; + while c <= b'z' { + char_map[Self::MODE_LOWER][c as usize] = c - b'a' + 2; + c += 1; } - // Try generating the character by switching to its mode. - if (!charInCurrentTable && SHIFT_TABLE[state.getMode()][mode] >= 0) { - // It never makes sense to temporarily shift to another mode if the - // character exists in the current mode. That can never save bits. - State shiftState = stateNoBinary.shiftAndAppend(mode, charInMode); - result.add(shiftState); + // for (int c = 'a'; c <= 'z'; c++) { + // char_map[Self::MODE_LOWER][c] = c - 'a' + 2; + // } + char_map[Self::MODE_DIGIT][b' ' as usize] = 1; + let mut c = b'0'; + while c <= b'9' { + char_map[Self::MODE_DIGIT][c as usize] = c - b'0' + 2; + c += 1; } - } - } - if (state.getBinaryShiftByteCount() > 0 || CHAR_MAP[state.getMode()][ch] == 0) { - // It's never worthwhile to go into binary shift mode if you're not already - // in binary shift mode, and the character exists in your current mode. - // That can never save bits over just outputting the char in the current mode. - State binaryState = state.addBinaryShiftChar(index); - result.add(binaryState); - } - } - - private static Collection updateStateListForPair(Iterable states, int index, int pairCode) { - Collection result = new LinkedList<>(); - for (State state : states) { - updateStateForPair(state, index, pairCode, result); - } - return simplifyStates(result); - } - - private static void updateStateForPair(State state, int index, int pairCode, Collection result) { - State stateNoBinary = state.endBinaryShift(index); - // Possibility 1. Latch to MODE_PUNCT, and then append this code - result.add(stateNoBinary.latchAndAppend(MODE_PUNCT, pairCode)); - if (state.getMode() != MODE_PUNCT) { - // 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 == 3 || pairCode == 4) { - // both characters are in DIGITS. Sometimes better to just add two digits - State digitState = stateNoBinary - .latchAndAppend(MODE_DIGIT, 16 - pairCode) // period or comma in DIGIT - .latchAndAppend(MODE_DIGIT, 1); // space in DIGIT - result.add(digitState); - } - if (state.getBinaryShiftByteCount() > 0) { - // It only makes sense to do the characters as binary if we're already - // in binary mode. - State binaryState = state.addBinaryShiftChar(index).addBinaryShiftChar(index + 1); - result.add(binaryState); - } - } - - private static Collection simplifyStates(Iterable states) { - Deque result = new LinkedList<>(); - for (State newState : states) { - boolean add = true; - for (Iterator iterator = result.iterator(); iterator.hasNext();) { - State oldState = iterator.next(); - if (oldState.isBetterThanOrEqualTo(newState)) { - add = false; - break; + // for (int c = '0'; c <= '9'; c++) { + // char_map[Self::MODE_DIGIT][c] = c - '0' + 2; + // } + char_map[Self::MODE_DIGIT][b',' as usize] = 12; + char_map[Self::MODE_DIGIT][b'.' as usize] = 13; + let mixedTable = [ + '\0', ' ', '\u{1}', '\u{2}', '\u{3}', '\u{4}', '\u{5}', '\u{6}', '\u{7}', '\u{8}', + '\t', '\n', '\u{13}', '\u{f}', '\r', '\u{33}', '\u{34}', '\u{35}', '\u{36}', '\u{37}', + '@', '\\', '^', '_', '`', '|', '~', '\u{177}', + ]; + let mut i = 0; + while i < mixedTable.len() { + char_map[Self::MODE_MIXED][mixedTable[i] as u8 as usize] = i as u8; + i += 1; } - if (newState.isBetterThanOrEqualTo(oldState)) { - iterator.remove(); + // for (int i = 0; i < mixedTable.length; i++) { + // CHAR_MAP[MODE_MIXED][mixedTable[i]] = i; + // } + let punctTable = [ + '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'', '(', ')', '*', + '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', ']', '{', '}', + ]; + let mut i = 0; + while i < punctTable.len() { + if punctTable[i] as u8 > 0u8 { + char_map[Self::MODE_PUNCT][punctTable[i] as u8 as usize] = i as u8; + } + i += 1; } - } - if (add) { - result.addFirst(newState); - } - } - return result; - } + // for (int i = 0; i < punctTable.length; i++) { + // if (punctTable[i] > 0) { + // CHAR_MAP[MODE_PUNCT][punctTable[i]] = i; + // } + // } + char_map + }; + // private static final int[][] CHAR_MAP = new int[5][256]; + // static { + // CHAR_MAP[MODE_UPPER][' '] = 1; + // for (int c = 'A'; c <= 'Z'; c++) { + // CHAR_MAP[MODE_UPPER][c] = c - 'A' + 2; + // } + // CHAR_MAP[MODE_LOWER][' '] = 1; + // for (int c = 'a'; c <= 'z'; c++) { + // CHAR_MAP[MODE_LOWER][c] = c - 'a' + 2; + // } + // CHAR_MAP[MODE_DIGIT][' '] = 1; + // for (int c = '0'; c <= '9'; c++) { + // CHAR_MAP[MODE_DIGIT][c] = c - '0' + 2; + // } + // CHAR_MAP[MODE_DIGIT][','] = 12; + // CHAR_MAP[MODE_DIGIT]['.'] = 13; + // int[] mixedTable = { + // '\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n', + // '\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^', + // '_', '`', '|', '~', '\177' + // }; + // for (int i = 0; i < mixedTable.length; i++) { + // CHAR_MAP[MODE_MIXED][mixedTable[i]] = i; + // } + // int[] punctTable = { + // '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'', + // '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', + // '[', ']', '{', '}' + // }; + // for (int i = 0; i < punctTable.length; i++) { + // if (punctTable[i] > 0) { + // CHAR_MAP[MODE_PUNCT][punctTable[i]] = i; + // } + // } + // } + + // A map showing the available shift codes. (The shifts to BINARY are not + // shown + pub const SHIFT_TABLE: [[i32; 6]; 6] = { + // mode shift codes, per table + let mut shift_table = [[-1i32; 6]; 6]; + + shift_table[Self::MODE_UPPER][Self::MODE_PUNCT] = 0; + + shift_table[Self::MODE_LOWER][Self::MODE_PUNCT] = 0; + shift_table[Self::MODE_LOWER][Self::MODE_UPPER] = 28; + + shift_table[Self::MODE_MIXED][Self::MODE_PUNCT] = 0; + + shift_table[Self::MODE_DIGIT][Self::MODE_PUNCT] = 0; + shift_table[Self::MODE_DIGIT][Self::MODE_UPPER] = 15; + + shift_table + }; + // const SHIFT_TABLE : [[u32]]= new int[6][6]; // mode shift codes, per table + // static { + // for (int[] table : SHIFT_TABLE) { + // Arrays.fill(table, -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; + // } + + pub fn new(text: Vec) -> Self { + Self { + text, + charset: encoding::all::UTF_8, + } + } + + pub fn with_charset(text: Vec, charset: &'static dyn encoding::Encoding) -> Self { + Self { text, charset } + } + + /** + * @return text represented by this encoder encoded as a {@link BitArray} + */ + pub fn encode(&self) -> Result { + let mut initialState = State::new(Token::new(), Self::MODE_UPPER as u32, 0, 0); + if let Some(eci) = CharacterSetECI::getCharacterSetECI(self.charset) { + initialState = initialState.appendFLGn(CharacterSetECI::getValue(&eci))?; + } else { + return Err(Exceptions::IllegalArgumentException( + "No ECI code for character set".to_owned(), + )); + } + // if self.charset != null { + // CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(charset); + // if (null == eci) { + // throw new IllegalArgumentException("No ECI code for character set " + charset); + // } + // initialState = initialState.appendFLGn(eci.getValue()); + // } + let mut states = vec![initialState]; + let mut index = 0; + while index < self.text.len() { + // for index in 0..self.text.len() { + // for (int index = 0; index < text.length; index++) { + let pairCode; + let nextChar = if index + 1 < self.text.len() { + self.text[index + 1] + } else { + 0 + }; + pairCode = match self.text[index] { + b'\r' if nextChar == b'\n' => 2, + b'.' if nextChar == b' ' => 3, + b',' if nextChar == b' ' => 4, + b':' if nextChar == b' ' => 5, + _ => 0, + }; + // switch (text[index]) { + // case '\r': + // pairCode = nextChar == '\n' ? 2 : 0; + // break; + // case '.' : + // pairCode = nextChar == ' ' ? 3 : 0; + // break; + // case ',' : + // pairCode = nextChar == ' ' ? 4 : 0; + // break; + // case ':' : + // pairCode = nextChar == ' ' ? 5 : 0; + // break; + // default: + // pairCode = 0; + // } + if pairCode > 0 { + // We have one of the four special PUNCT pairs. Treat them specially. + // Get a new set of states for the two new characters. + states = Self::updateStateListForPair(states, index as u32, pairCode); + index += 1; + } else { + // Get a new set of states for the new character. + states = self.updateStateListForChar(states, index as u32); + } + index += 1; + } + // We are left with a set of states. Find the shortest one. + let minState = states + .into_iter() + .min_by(|a, b| { + let diff: i64 = a.getBitCount() as i64 - b.getBitCount() as i64; + if diff < 0 { + Ordering::Less + } else if diff == 0 { + Ordering::Equal + } else { + Ordering::Greater + } + // a.getBitCount() - b.getBitCount() + }) + .unwrap(); + // let minState = Collections.min(states, new Comparator() { + // @Override + // public int compare(State a, State b) { + // return a.getBitCount() - b.getBitCount(); + // } + // }); + // Convert it to a bit array, and return. + Ok(minState.toBitArray(&self.text)) + } + + // We update a set of states for a new character by updating each state + // for the new character, merging the results, and then removing the + // non-optimal states. + fn updateStateListForChar(&self, states: Vec, index: u32) -> Vec { + let mut result = Vec::new(); + for state in states { + // for (State state : states) { + self.updateStateForChar(state, index, &mut result); + } + Self::simplifyStates(result) + } + + // Return a set of states that represent the possible ways of updating this + // state for the next character. The resulting set of states are added to + // the "result" list. + fn updateStateForChar(&self, state: State, index: u32, result: &mut Vec) { + let ch = self.text[index as usize]; + let charInCurrentTable = Self::CHAR_MAP[state.getMode() as usize][ch as usize] > 0; + let mut stateNoBinary = None; + for mode in 0..Self::MODE_PUNCT { + // for (int mode = 0; mode <= MODE_PUNCT; mode++) { + let charInMode = Self::CHAR_MAP[mode as usize][ch as usize]; + if charInMode > 0 { + if stateNoBinary.is_none() { + // Only create stateNoBinary the first time it's required. + stateNoBinary = Some(state.clone().endBinaryShift(index)); + } + // Try generating the character by latching to its mode + if !charInCurrentTable || mode as u32 == state.getMode() || mode == Self::MODE_DIGIT + { + // If the character is in the current table, we don't want to latch to + // any other mode except possibly digit (which uses only 4 bits). Any + // other latch would be equally successful *after* this character, and + // so wouldn't save any bits. + let latchState = stateNoBinary + .clone() + .unwrap() + .latchAndAppend(mode as u32, charInMode as u32); + result.push(latchState); + } + // Try generating the character by switching to its mode. + if !charInCurrentTable && Self::SHIFT_TABLE[state.getMode() as usize][mode] >= 0 { + // It never makes sense to temporarily shift to another mode if the + // character exists in the current mode. That can never save bits. + let shiftState = stateNoBinary + .clone() + .unwrap() + .shiftAndAppend(mode as u32, charInMode as u32); + result.push(shiftState); + } + } + } + if state.getBinaryShiftByteCount() > 0 + || Self::CHAR_MAP[state.getMode() as usize][ch as usize] == 0 + { + // It's never worthwhile to go into binary shift mode if you're not already + // in binary shift mode, and the character exists in your current mode. + // That can never save bits over just outputting the char in the current mode. + let binaryState = state.addBinaryShiftChar(index); + result.push(binaryState); + } + } + + fn updateStateListForPair(states: Vec, index: u32, pairCode: u32) -> Vec { + let mut result = Vec::new(); + for state in states { + // for (State state : states) { + Self::updateStateForPair(state, index, pairCode, &mut result); + } + + Self::simplifyStates(result) + } + + fn updateStateForPair(state: State, index: u32, pairCode: u32, result: &mut Vec) { + let stateNoBinary = state.clone().endBinaryShift(index); + // Possibility 1. Latch to MODE_PUNCT, and then append this code + result.push( + stateNoBinary + .clone() + .latchAndAppend(Self::MODE_PUNCT as u32, pairCode), + ); + if state.getMode() != Self::MODE_PUNCT as u32 { + // Possibility 2. Shift to MODE_PUNCT, and then append this code. + // Every state except MODE_PUNCT (handled above) can shift + result.push( + stateNoBinary + .clone() + .shiftAndAppend(Self::MODE_PUNCT as u32, pairCode), + ); + } + if pairCode == 3 || pairCode == 4 { + // both characters are in DIGITS. Sometimes better to just add two digits + let digitState = stateNoBinary + .latchAndAppend(Self::MODE_DIGIT as u32, 16 - pairCode) // period or comma in DIGIT + .latchAndAppend(Self::MODE_DIGIT as u32, 1); // space in DIGIT + result.push(digitState); + } + if state.getBinaryShiftByteCount() > 0 { + // It only makes sense to do the characters as binary if we're already + // in binary mode. + let binaryState = state + .addBinaryShiftChar(index) + .addBinaryShiftChar(index + 1); + result.push(binaryState); + } + } + + fn simplifyStates(states: Vec) -> Vec { + let mut result: Vec = Vec::new(); + for newState in states { + // for (State newState : states) { + let mut add = true; + for i in 0..result.len() { + // for st in result { + // for (Iterator iterator = result.iterator(); iterator.hasNext();) { + let oldState = result.get(i).unwrap(); + if oldState.isBetterThanOrEqualTo(&newState) { + add = false; + break; + } + if newState.isBetterThanOrEqualTo(&oldState) { + result.remove(i); + } + } + if add { + result.push(newState); + } + } + return result; + } } diff --git a/src/aztec/encoder/mod.rs b/src/aztec/encoder/mod.rs index 27434ad..3c9fd5c 100644 --- a/src/aztec/encoder/mod.rs +++ b/src/aztec/encoder/mod.rs @@ -5,6 +5,8 @@ mod binary_shift_token; mod state; mod high_level_encoder; +pub mod encoder; + pub use aztec_code::*; pub use token::*; pub use simple_token::*; diff --git a/src/aztec/encoder/simple_token.rs b/src/aztec/encoder/simple_token.rs index fe7f599..b3be35f 100644 --- a/src/aztec/encoder/simple_token.rs +++ b/src/aztec/encoder/simple_token.rs @@ -18,7 +18,7 @@ use std::fmt; use crate::common::BitArray; -#[derive(Debug,PartialEq, Eq,Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct SimpleToken { // For normal words, indicates value and bitCount value: u16, @@ -34,7 +34,9 @@ impl SimpleToken { } pub fn appendTo(&self, bitArray: &mut BitArray, text: &[u8]) { - bitArray.appendBits(self.value as u32, self.bitCount as usize).expect("append should never fail"); + bitArray + .appendBits(self.value as u32, self.bitCount as usize) + .expect("append should never fail"); } // @Override diff --git a/src/aztec/encoder/state.rs b/src/aztec/encoder/state.rs index 423579b..569fcb9 100644 --- a/src/aztec/encoder/state.rs +++ b/src/aztec/encoder/state.rs @@ -16,197 +16,241 @@ use std::fmt; -use crate::{exceptions::Exceptions, common::BitArray}; +use encoding::Encoding; -use super::{Token, TokenType}; +use crate::{common::BitArray, exceptions::Exceptions}; +use super::{HighLevelEncoder, Token}; /** * State represents all information about a sequence necessary to generate the current output. * Note that a state is immutable. */ +#[derive(Debug, Clone, PartialEq, Eq)] pub struct State { + // static final State INITIAL_STATE = new State(Token.EMPTY, HighLevelEncoder.MODE_UPPER, 0, 0); - // static final State INITIAL_STATE = new State(Token.EMPTY, HighLevelEncoder.MODE_UPPER, 0, 0); - - // The current mode of the encoding (or the mode to which we'll return if - // we're in Binary Shift mode. - mode:u32, - // The list of tokens that we output. If we are in Binary Shift mode, this - // token list does *not* yet included the token for those bytes - token:Token, - // If non-zero, the number of most recent bytes that should be output - // in Binary Shift mode. - binaryShiftByteCount:u32, - // The total number of bits generated (including Binary Shift). - bitCount:u32, - binaryShiftCost:u32, + // The current mode of the encoding (or the mode to which we'll return if + // we're in Binary Shift mode. + mode: u32, + // The list of tokens that we output. If we are in Binary Shift mode, this + // token list does *not* yet included the token for those bytes + token: Token, + // If non-zero, the number of most recent bytes that should be output + // in Binary Shift mode. + binaryShiftByteCount: u32, + // The total number of bits generated (including Binary Shift). + bitCount: u32, + binaryShiftCost: u32, } - impl State { - pub fn new( token:Token, mode:u32, binaryBytes:u32, bitCount:u32) -> Self{ - Self{ - mode, - token, - binaryShiftByteCount: binaryBytes, - bitCount, - binaryShiftCost: Self::calculateBinaryShiftCost(binaryBytes), +impl State { + pub fn new(token: Token, mode: u32, binaryBytes: u32, bitCount: u32) -> Self { + Self { + mode, + token, + binaryShiftByteCount: binaryBytes, + bitCount, + binaryShiftCost: Self::calculateBinaryShiftCost(binaryBytes), + } } - } - pub fn getMode(&self) -> u32{ - self.mode - } - - pub fn getToken(&self) -> &Token{ - &self.token - } - - pub fn getBinaryShiftByteCount(&self) -> u32{ - self.binaryShiftByteCount - } - - pub fn getBitCount(&self) -> u32{ - self.bitCount - } - - pub fn appendFLGn(&self, eci:u32) -> Result { - let result = self.shiftAndAppend(HighLevelEncoder::MODE_PUNCT, 0); // 0: FLG(n) - let token = result.token; - let bitsAdded = 3; - if eci < 0 { - token.add(0, 3); // 0: FNC1 - } else if eci > 999999 { - return Err(Exceptions::IllegalArgumentException("ECI code must be between 0 and 999999".to_owned())); - // throw new IllegalArgumentException("ECI code must be between 0 and 999999"); - } else { - let eciDigits = Integer.toString(eci).getBytes(StandardCharsets.ISO_8859_1); - token.add(eciDigits.length, 3); // 1-6: number of ECI digits - for eciDigit in eciDigits { - // for (byte eciDigit : eciDigits) { - token.add(eciDigit - '0' + 2, 4); - } - bitsAdded += eciDigits.length * 4; + pub fn getMode(&self) -> u32 { + self.mode } - Ok(State::new(token, self.mode, 0, self.bitCount + bitsAdded)) - // return new State(token, mode, 0, bitCount + bitsAdded); - } - // Create a new state representing this state with a latch to a (not - // necessary different) mode, and then a code. - pub fn latchAndAppend(&self, mode:u32, value:u32) -> State{ - let bitCount = self.bitCount; - let token = self.token; - if mode != self.mode { - let latch = HighLevelEncoder.LATCH_TABLE[this.mode][mode]; - token.add(latch & 0xFFFF, latch >> 16); - bitCount += latch >> 16; + pub fn getToken(&self) -> &Token { + &self.token } - let latchModeBitCount = mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5; - token.add(value, latchModeBitCount); - - State::new(token, mode, 0, bitCount + latchModeBitCount) - } - // Create a new state representing this state, with a temporary shift - // to a different mode to output a single value. - pub fn shiftAndAppend(&self, mode:u32, value:u32) -> State{ - let token = this.token; - let thisModeBitCount = this.mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5; - // Shifts exist only to UPPER and PUNCT, both with tokens size 5. - token = token.add(HighLevelEncoder.SHIFT_TABLE[this.mode][mode], thisModeBitCount); - token = token.add(value, 5); - State::new(token, this.mode, 0, this.bitCount + thisModeBitCount + 5) - } + pub fn getBinaryShiftByteCount(&self) -> u32 { + self.binaryShiftByteCount + } - // Create a new state representing this state, but an additional character - // output in Binary Shift mode. - pub fn addBinaryShiftChar(&self, index:u32) -> State{ - let token = this.token; - let mode = this.mode; - let bitCount = this.bitCount; - if self.mode == HighLevelEncoder.MODE_PUNCT || self.mode == HighLevelEncoder.MODE_DIGIT { - let latch = HighLevelEncoder.LATCH_TABLE[mode][HighLevelEncoder.MODE_UPPER]; - token.add(latch & 0xFFFF, latch >> 16); - bitCount += latch >> 16; - mode = HighLevelEncoder.MODE_UPPER; + pub fn getBitCount(&self) -> u32 { + self.bitCount } - let deltaBitCount = - (binaryShiftByteCount == 0 || binaryShiftByteCount == 31) ? 18 : - (binaryShiftByteCount == 62) ? 9 : 8; - let result = State::new(token, mode, binaryShiftByteCount + 1, bitCount + deltaBitCount); - if (result.binaryShiftByteCount == 2047 + 31) { - // The string is as long as it's allowed to be. We should end it. - result = result.endBinaryShift(index + 1); - } - result - } - // Create the state identical to this one, but we are no longer in - // Binary Shift mode. - pub fn endBinaryShift(self, index:u32) -> State{ - if self.binaryShiftByteCount == 0 { - return self; + pub fn appendFLGn(self, eci: u32) -> Result { + let bit_count = self.bitCount; + let mode = self.mode; + let result = self.shiftAndAppend(HighLevelEncoder::MODE_PUNCT as u32, 0); // 0: FLG(n) + let mut token = result.token; + let mut bitsAdded = 3; + if eci < 0 { + token.add(0, 3); // 0: FNC1 + } else if eci > 999999 { + return Err(Exceptions::IllegalArgumentException( + "ECI code must be between 0 and 999999".to_owned(), + )); + // throw new IllegalArgumentException("ECI code must be between 0 and 999999"); + } else { + let eciDigits = encoding::all::ISO_8859_1 + .encode(&format!("{}", eci), encoding::EncoderTrap::Replace) + .unwrap(); + // let eciDigits = Integer.toString(eci).getBytes(StandardCharsets.ISO_8859_1); + token.add(eciDigits.len() as i32, 3); // 1-6: number of ECI digits + for eciDigit in &eciDigits { + // for (byte eciDigit : eciDigits) { + token.add((eciDigit - b'0' + 2) as i32, 4); + } + bitsAdded += eciDigits.len() * 4; + } + Ok(State::new(token, mode, 0, bit_count + bitsAdded as u32)) + // return new State(token, mode, 0, bitCount + bitsAdded); } - let token = self.token; - self.token.addBinaryShift(index - self.binaryShiftByteCount, self.binaryShiftByteCount); - - State::new(token, self.mode, 0, self.bitCount) - } - // Returns true if "this" state is better (or equal) to be in than "that" - // state under all possible circumstances. - pub fn isBetterThanOrEqualTo(&self, other:&State)->bool { - let newModeBitCount = self.bitCount + (HighLevelEncoder.LATCH_TABLE[this.mode][other.mode] >> 16); - if self.binaryShiftByteCount < other.binaryShiftByteCount { - // add additional B/S encoding cost of other, if any - newModeBitCount += other.binaryShiftCost - self.binaryShiftCost; - } else if self.binaryShiftByteCount > other.binaryShiftByteCount && other.binaryShiftByteCount > 0 { - // maximum possible additional cost (we end up exceeding the 31 byte boundary and other state can stay beneath it) - newModeBitCount += 10; - } - newModeBitCount <= other.bitCount - } + // Create a new state representing this state with a latch to a (not + // necessary different) mode, and then a code. + pub fn latchAndAppend(self, mode: u32, value: u32) -> State { + let mut bitCount = self.bitCount; + let mut token = self.token; + if mode != self.mode { + let latch = HighLevelEncoder::LATCH_TABLE[self.mode as usize][mode as usize]; + token.add(latch as i32 & 0xFFFF, latch >> 16); + bitCount += latch >> 16; + } + let latchModeBitCount = if mode == HighLevelEncoder::MODE_DIGIT as u32 { + 4 + } else { + 5 + }; + token.add(value as i32, latchModeBitCount); - pub fn toBitArray(&self, text:&[u8]) -> BitArray{ - let symbols = Vec::new(); - let mut tok = self.endBinaryShift(text.len() as u32).token; - let mut tkn = tok.getPrevious(); - while tkn != &TokenType::Empty { - // for (Token token = endBinaryShift(text.length).token; token != null; token = token.getPrevious()) { - symbols.push(tkn); - tkn = tok.getPrevious(); + State::new(token, mode, 0, bitCount + latchModeBitCount) } - let bitArray = BitArray::new(); - // Add each token to the result in forward order - for i in (0..symbols.len()-1).rev() { - // for (int i = symbols.size() - 1; i >= 0; i--) { - symbols.get(i).unwrap().appendTo(bitArray, text); - } - bitArray - } - // @Override - // public String toString() { - // return String.format("%s bits=%d bytes=%d", HighLevelEncoder.MODE_NAMES[mode], bitCount, binaryShiftByteCount); - // } + // Create a new state representing this state, with a temporary shift + // to a different mode to output a single value. + pub fn shiftAndAppend(self, mode: u32, value: u32) -> State { + let mut token = self.token; + let thisModeBitCount = if self.mode == HighLevelEncoder::MODE_DIGIT as u32 { + 4 + } else { + 5 + }; + // Shifts exist only to UPPER and PUNCT, both with tokens size 5. + token.add( + HighLevelEncoder::SHIFT_TABLE[self.mode as usize][mode as usize], + thisModeBitCount, + ); + token.add(value as i32, 5); + State::new(token, self.mode, 0, self.bitCount + thisModeBitCount + 5) + } - fn calculateBinaryShiftCost( binaryShiftByteCount:u32) -> u32{ - if binaryShiftByteCount > 62 { - return 21; // B/S with extended length + // Create a new state representing this state, but an additional character + // output in Binary Shift mode. + pub fn addBinaryShiftChar(self, index: u32) -> State { + let mut token = self.token; + let mut mode = self.mode; + let mut bitCount = self.bitCount; + if self.mode == HighLevelEncoder::MODE_PUNCT as u32 + || self.mode == HighLevelEncoder::MODE_DIGIT as u32 + { + let latch = HighLevelEncoder::LATCH_TABLE[mode as usize][HighLevelEncoder::MODE_UPPER]; + token.add(latch as i32 & 0xFFFF, latch >> 16); + bitCount += latch >> 16; + mode = HighLevelEncoder::MODE_UPPER as u32; + } + let deltaBitCount = if self.binaryShiftByteCount == 0 || self.binaryShiftByteCount == 31 { + 18 + } else { + if self.binaryShiftByteCount == 62 { + 9 + } else { + 8 + } + }; + let mut result = State::new( + token, + mode, + self.binaryShiftByteCount + 1, + bitCount + deltaBitCount, + ); + if result.binaryShiftByteCount == 2047 + 31 { + // The string is as long as it's allowed to be. We should end it. + result = result.endBinaryShift(index + 1); + } + result } - if binaryShiftByteCount > 31 { - return 20; // two B/S - } - if binaryShiftByteCount > 0 { - return 10; // one B/S - } - return 0; - } + // Create the state identical to this one, but we are no longer in + // Binary Shift mode. + pub fn endBinaryShift(self, index: u32) -> State { + if self.binaryShiftByteCount == 0 { + return self; + } + let mut token = self.token; + token.addBinaryShift(index - self.binaryShiftByteCount, self.binaryShiftByteCount); + + State::new(token, self.mode, 0, self.bitCount) + } + + // Returns true if "this" state is better (or equal) to be in than "that" + // state under all possible circumstances. + pub fn isBetterThanOrEqualTo(&self, other: &State) -> bool { + let mut newModeBitCount = self.bitCount + + (HighLevelEncoder::LATCH_TABLE[self.mode as usize][other.mode as usize] >> 16); + if self.binaryShiftByteCount < other.binaryShiftByteCount { + // add additional B/S encoding cost of other, if any + newModeBitCount += other.binaryShiftCost - self.binaryShiftCost; + } else if self.binaryShiftByteCount > other.binaryShiftByteCount + && other.binaryShiftByteCount > 0 + { + // maximum possible additional cost (we end up exceeding the 31 byte boundary and other state can stay beneath it) + newModeBitCount += 10; + } + newModeBitCount <= other.bitCount + } + + pub fn toBitArray(self, text: &[u8]) -> BitArray { + let mut symbols = Vec::new(); + let tok = self.endBinaryShift(text.len() as u32).token; + for tkn in tok.into_iter() { + // for (Token token = endBinaryShift(text.length).token; token != null; token = token.getPrevious()) { + symbols.push(tkn); + } + // let mut tkn = tok.getPrevious(); + // while tkn != &TokenType::Empty { + // // for (Token token = endBinaryShift(text.length).token; token != null; token = token.getPrevious()) { + // symbols.push(tkn); + // tkn = tok.getPrevious(); + // } + let mut bitArray = BitArray::new(); + // Add each token to the result in forward order + for i in (0..symbols.len() - 1).rev() { + // for (int i = symbols.size() - 1; i >= 0; i--) { + symbols.get(i).unwrap().appendTo(&mut bitArray, text); + } + bitArray + } + + // @Override + // public String toString() { + // return String.format("%s bits=%d bytes=%d", HighLevelEncoder.MODE_NAMES[mode], bitCount, binaryShiftByteCount); + // } + + fn calculateBinaryShiftCost(binaryShiftByteCount: u32) -> u32 { + if binaryShiftByteCount > 62 { + return 21; // B/S with extended length + } + if binaryShiftByteCount > 31 { + return 20; // two B/S + } + if binaryShiftByteCount > 0 { + return 10; // one B/S + } + return 0; + } } impl fmt::Display for State { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f,"{} bits={} bytes={}", HighLevelEncoder::MODE_NAMES[mode], self.bitCount, self.binaryShiftByteCount) + write!( + f, + "{} bits={} bytes={}", + HighLevelEncoder::MODE_NAMES[self.mode as usize], + self.bitCount, + self.binaryShiftByteCount + ) } -} \ No newline at end of file +} diff --git a/src/aztec/encoder/token.rs b/src/aztec/encoder/token.rs index 4a42647..30b1032 100644 --- a/src/aztec/encoder/token.rs +++ b/src/aztec/encoder/token.rs @@ -18,39 +18,83 @@ use std::rc::Rc; use crate::common::BitArray; -use super::{SimpleToken, BinaryShiftToken}; +use super::{BinaryShiftToken, SimpleToken}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TokenType { - Simple(SimpleToken), BinaryShift(BinaryShiftToken), Empty + Simple(SimpleToken), + BinaryShift(BinaryShiftToken), + Empty, } +impl TokenType { + pub fn appendTo(&self, bit_array: &mut BitArray, text: &[u8]) { + // let token = &self.tokens[self.current_pointer]; + match self { + TokenType::Simple(a) => a.appendTo(bit_array, text), + TokenType::BinaryShift(a) => a.appendTo(bit_array, text), + TokenType::Empty => panic!("cannot appendTo on Empty final item"), + } + } +} + +#[derive(Debug,Clone,PartialEq, Eq)] pub struct Token { - tokens: Vec, - current_pointer: usize, + tokens: Vec, + //current_pointer: usize, } impl Token { - pub fn new () -> Self { - Self { - tokens: vec![TokenType::Empty], - current_pointer: 0, + pub fn new() -> Self { + Self { + tokens: Vec::new(), + //current_pointer: 0, + } + } + // pub fn getPrevious(&mut self) -> &TokenType { + // self.current_pointer -= 1; + // &self.tokens[self.current_pointer] + // } + pub fn add(&mut self, value: i32, bit_count: u32) { + //self.current_pointer += 1; + self.tokens + .push(TokenType::Simple(SimpleToken::new(value, bit_count))); + // &self.tokens[self.current_pointer] + } + pub fn addBinaryShift(&mut self, start: u32, byte_count: u32) { + //self.current_pointer += 1; + self.tokens + .push(TokenType::BinaryShift(BinaryShiftToken::new( + start, byte_count, + ))); + // &self.tokens[self.current_pointer] + } +} + +pub struct TokenIter { + src: Vec, + // pos: usize, +} + +impl Iterator for TokenIter { + type Item = TokenType; + + fn next(&mut self) -> Option { + self.src.pop() + } +} + +impl IntoIterator for Token { + type Item = TokenType; + + type IntoIter = TokenIter; + + fn into_iter(self) -> Self::IntoIter { + TokenIter { + src: self.tokens, + // pos: self.current_pointer, + } } - } - pub fn getPrevious(&mut self) -> &TokenType{ - self.current_pointer -= 1; - &self.tokens[self.current_pointer] - } - pub fn add(&mut self, value:i32, bit_count: u32,) -> &TokenType { - self.current_pointer+=1; - self.tokens.push(TokenType::Simple(SimpleToken::new(value,bit_count))); - &self.tokens[self.current_pointer] - } - pub fn addBinaryShift(&mut self, start: u32, byte_count: u32) -> &TokenType { - self.current_pointer+=1; - self.tokens.push(TokenType::BinaryShift(BinaryShiftToken::new(start,byte_count))); - &self.tokens[self.current_pointer] - } } // pub enum Token{ diff --git a/src/aztec/mod.rs b/src/aztec/mod.rs index 454f36d..2a888c8 100644 --- a/src/aztec/mod.rs +++ b/src/aztec/mod.rs @@ -1,13 +1,18 @@ mod AztecDetectorResult; +mod aztec_reader; +mod aztec_writer; pub mod decoder; pub mod detector; pub mod encoder; +pub use aztec_reader::*; +pub use aztec_writer::*; + #[cfg(test)] mod DecoderTest; -// #[cfg(test)] -// mod EncoderTest; -// #[cfg(test)] -// mod DetectorTest; +#[cfg(test)] +mod EncoderTest; +#[cfg(test)] +mod DetectorTest; mod shared_test_methods; \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs index 02eab35..136430e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -770,7 +770,7 @@ pub trait DetectorRXingResult { pub struct BitMatrix { width: u32, height: u32, - rowSize: usize, + row_size: usize, bits: Vec, } @@ -799,7 +799,7 @@ impl BitMatrix { Ok(Self { width, height, - rowSize: ((width + 31) / 32) as usize, + row_size: ((width + 31) / 32) as usize, bits: vec![0; (((width + 31) / 32) * height) as usize], }) // this.width = width; @@ -812,7 +812,7 @@ impl BitMatrix { Self { width, height, - rowSize, + row_size: rowSize, bits, } } @@ -926,7 +926,7 @@ impl BitMatrix { * @return value of given bit in matrix */ pub fn get(&self, x: u32, y: u32) -> bool { - let offset = y as usize * self.rowSize + (x as usize / 32); + let offset = y as usize * self.row_size + (x as usize / 32); return ((self.bits[offset] >> (x & 0x1f)) & 1) != 0; } @@ -937,12 +937,12 @@ impl BitMatrix { * @param y The vertical component (i.e. which row) */ pub fn set(&mut self, x: u32, y: u32) { - let offset = y as usize * self.rowSize + (x as usize / 32); + let offset = y as usize * self.row_size + (x as usize / 32); self.bits[offset] |= 1 << (x & 0x1f); } pub fn unset(&mut self, x: u32, y: u32) { - let offset = y as usize * self.rowSize + (x as usize / 32); + let offset = y as usize * self.row_size + (x as usize / 32); self.bits[offset] &= !(1 << (x & 0x1f)); } @@ -953,7 +953,7 @@ impl BitMatrix { * @param y The vertical component (i.e. which row) */ pub fn flip_coords(&mut self, x: u32, y: u32) { - let offset = y as usize * self.rowSize + (x as usize / 32); + let offset = y as usize * self.row_size + (x as usize / 32); self.bits[offset] ^= 1 << (x & 0x1f); } @@ -975,7 +975,7 @@ impl BitMatrix { * @param mask XOR mask */ pub fn xor(&mut self, mask: &BitMatrix) -> Result<(), Exceptions> { - if self.width != mask.width || self.height != mask.height || self.rowSize != mask.rowSize { + if self.width != mask.width || self.height != mask.height || self.row_size != mask.row_size { return Err(Exceptions::IllegalArgumentException( "input matrix dimensions do not match".to_owned(), )); @@ -983,10 +983,10 @@ impl BitMatrix { let rowArray = BitArray::with_size(self.width as usize); for y in 0..self.height { //for (int y = 0; y < height; y++) { - let offset = y as usize * self.rowSize; + let offset = y as usize * self.row_size; let tmp = mask.getRow(y, &rowArray); let row = tmp.getBitArray(); - for x in 0..self.rowSize { + for x in 0..self.row_size { //for (int x = 0; x < rowSize; x++) { self.bits[offset + x] ^= row[x]; } @@ -1039,7 +1039,7 @@ impl BitMatrix { } for y in top..bottom { //for (int y = top; y < bottom; y++) { - let offset = y as usize * self.rowSize; + let offset = y as usize * self.row_size; for x in left..right { //for (int x = left; x < right; x++) { self.bits[offset + (x as usize / 32)] |= 1 << (x & 0x1f); @@ -1067,8 +1067,8 @@ impl BitMatrix { // row.clone() }; - let offset = y as usize * self.rowSize; - for x in 0..self.rowSize { + let offset = y as usize * self.row_size; + for x in 0..self.row_size { //for (int x = 0; x < rowSize; x++) { rw.setBulk(x * 32, self.bits[offset + x]); } @@ -1080,8 +1080,8 @@ impl BitMatrix { * @param row {@link BitArray} to copy from */ pub fn setRow(&mut self, y: u32, row: &BitArray) { - return self.bits[y as usize * self.rowSize..y as usize * self.rowSize + self.rowSize] - .clone_from_slice(&row.getBitArray()[0..self.rowSize]); + return self.bits[y as usize * self.row_size..y as usize * self.row_size + self.row_size] + .clone_from_slice(&row.getBitArray()[0..self.row_size]); //System.arraycopy(row.getBitArray(), 0, self.bits, y * self.rowSize, self.rowSize); } @@ -1144,7 +1144,7 @@ impl BitMatrix { //for (int y = 0; y < height; y++) { for x in 0..self.width { //for (int x = 0; x < width; x++) { - let offset = y as usize * self.rowSize + (x as usize / 32); + let offset = y as usize * self.row_size + (x as usize / 32); if ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 { let newOffset: usize = ((newHeight - 1 - x) * newRowSize + (y / 32)) .try_into() @@ -1155,7 +1155,7 @@ impl BitMatrix { } self.width = newWidth; self.height = newHeight; - self.rowSize = newRowSize.try_into().unwrap(); + self.row_size = newRowSize.try_into().unwrap(); self.bits = newBits; } @@ -1174,9 +1174,9 @@ impl BitMatrix { for y in 0..self.height { //for (int y = 0; y < height; y++) { - for x32 in 0..self.rowSize { + for x32 in 0..self.row_size { //for (int x32 = 0; x32 < rowSize; x32++) { - let theBits = self.bits[y as usize * self.rowSize + x32]; + let theBits = self.bits[y as usize * self.row_size + x32]; if theBits != 0 { if y < top { top = y; @@ -1226,8 +1226,8 @@ impl BitMatrix { if bitsOffset == self.bits.len() { return None; } - let y = bitsOffset / self.rowSize; - let mut x = (bitsOffset % self.rowSize) * 32; + let y = bitsOffset / self.row_size; + let mut x = (bitsOffset % self.row_size) * 32; let theBits = self.bits[bitsOffset]; let mut bit = 0; @@ -1247,8 +1247,8 @@ impl BitMatrix { return None; } - let y = bitsOffset as usize / self.rowSize; - let mut x = (bitsOffset as usize % self.rowSize) * 32; + let y = bitsOffset as usize / self.row_size; + let mut x = (bitsOffset as usize % self.row_size) * 32; let theBits = self.bits[bitsOffset as usize]; let mut bit = 31; @@ -1278,7 +1278,7 @@ impl BitMatrix { * @return The row size of the matrix */ pub fn getRowSize(&self) -> usize { - return self.rowSize; + return self.row_size; } // @Override diff --git a/src/lib.rs b/src/lib.rs index bcecee7..b7881c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,12 @@ +mod aztec; +mod client; mod common; mod exceptions; -mod client; -mod aztec; -#[cfg(feature="image")] +#[cfg(feature = "image")] mod BufferedImageLuminanceSource; -#[cfg(feature="image")] +#[cfg(feature = "image")] pub use BufferedImageLuminanceSource::*; use crate::common::{BitArray, BitMatrix}; @@ -45,8 +45,8 @@ mod RGBLuminanceSourceTestCase; * * @author Sean Owen */ -#[derive(Debug,PartialEq, Eq,Hash)] - pub enum BarcodeFormat { +#[derive(Debug, PartialEq, Eq, Hash,Clone, Copy)] +pub enum BarcodeFormat { /** Aztec 2D barcode format. */ AZTEC, @@ -122,6 +122,7 @@ mod RGBLuminanceSourceTestCase; * * @author dswitkin@google.com (Daniel Switkin) */ +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum EncodeHintType { /** * Specifies what degree of error correction to use, for example in QR Codes. @@ -274,6 +275,158 @@ pub enum EncodeHintType { CODE128_COMPACT, } +pub enum EncodeHintValue { + /** + * Specifies what degree of error correction to use, for example in QR Codes. + * Type depends on the encoder. For example for QR codes it's type + * {@link com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ErrorCorrectionLevel}. + * For Aztec it is of type {@link Integer}, representing the minimal percentage of error correction words. + * For PDF417 it is of type {@link Integer}, valid values being 0 to 8. + * In all cases, it can also be a {@link String} representation of the desired value as well. + * Note: an Aztec symbol should have a minimum of 25% EC words. + */ + ErrorCorrection(String), + + /** + * Specifies what character encoding to use where applicable (type {@link String}) + */ + CharacterSet(String), + + /** + * Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint}) + */ + DataMatrixShape, + + /** + * Specifies whether to use compact mode for Data Matrix (type {@link Boolean}, or "true" or "false" + * {@link String } value). + * The compact encoding mode also supports the encoding of characters that are not in the ISO-8859-1 + * character set via ECIs. + * Please note that in that case, the most compact character encoding is chosen for characters in + * the input that are not in the ISO-8859-1 character set. Based on experience, some scanners do not + * support encodings like cp-1256 (Arabic). In such cases the encoding can be forced to UTF-8 by + * means of the {@link #CHARACTER_SET} encoding hint. + * Compact encoding also provides GS1-FNC1 support when {@link #GS1_FORMAT} is selected. In this case + * group-separator character (ASCII 29 decimal) can be used to encode the positions of FNC1 codewords + * for the purpose of delimiting AIs. + * This option and {@link #FORCE_C40} are mutually exclusive. + */ + DataMatrixCompact(String), + + /** + * Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now. + * + * @deprecated use width/height params in + * {@link com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int)} + */ + #[deprecated] + MinSize, + + /** + * Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now. + * + * @deprecated without replacement + */ + #[deprecated] + MaxSize(Dimension), + + /** + * Specifies margin, in pixels, to use when generating the barcode. The meaning can vary + * by format; for example it controls margin before and after the barcode horizontally for + * most 1D formats. (Type {@link Integer}, or {@link String} representation of the integer value). + */ + Margin(String), + + /** + * Specifies whether to use compact mode for PDF417 (type {@link Boolean}, or "true" or "false" + * {@link String} value). + */ + Pdf417Compact(String), + + /** + * Specifies what compaction mode to use for PDF417 (type + * {@link com.google.zxing.pdf417.encoder.Compaction Compaction} or {@link String} value of one of its + * enum values). + */ + Pdf417Compaction(String), + + /** + * Specifies the minimum and maximum number of rows and columns for PDF417 (type + * {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}). + */ + Pdf417Dimensions, + + /** + * Specifies whether to automatically insert ECIs when encoding PDF417 (type {@link Boolean}, or "true" or "false" + * {@link String} value). + * Please note that in that case, the most compact character encoding is chosen for characters in + * the input that are not in the ISO-8859-1 character set. Based on experience, some scanners do not + * support encodings like cp-1256 (Arabic). In such cases the encoding can be forced to UTF-8 by + * means of the {@link #CHARACTER_SET} encoding hint. + */ + Pdf417AutoEci(String), + + /** + * Specifies the required number of layers for an Aztec code. + * A negative number (-1, -2, -3, -4) specifies a compact Aztec code. + * 0 indicates to use the minimum number of layers (the default). + * A positive number (1, 2, .. 32) specifies a normal (non-compact) Aztec code. + * (Type {@link Integer}, or {@link String} representation of the integer value). + */ + AztecLayers(u32), + + /** + * Specifies the exact version of QR code to be encoded. + * (Type {@link Integer}, or {@link String} representation of the integer value). + */ + QrVersion(String), + + /** + * Specifies the QR code mask pattern to be used. Allowed values are + * 0..QRCode.NUM_MASK_PATTERNS-1. By default the code will automatically select + * the optimal mask pattern. + * * (Type {@link Integer}, or {@link String} representation of the integer value). + */ + QrMaskPattern(String), + + /** + * Specifies whether to use compact mode for QR code (type {@link Boolean}, or "true" or "false" + * {@link String } value). + * Please note that when compaction is performed, the most compact character encoding is chosen + * for characters in the input that are not in the ISO-8859-1 character set. Based on experience, + * some scanners do not support encodings like cp-1256 (Arabic). In such cases the encoding can + * be forced to UTF-8 by means of the {@link #CHARACTER_SET} encoding hint. + */ + QrCompact(String), + + /** + * Specifies whether the data should be encoded to the GS1 standard (type {@link Boolean}, or "true" or "false" + * {@link String } value). + */ + Gs1Format(String), + + /** + * Forces which encoding will be used. Currently only used for Code-128 code sets (Type {@link String}). + * Valid values are "A", "B", "C". + * This option and {@link #CODE128_COMPACT} are mutually exclusive. + */ + ForceCodeSet(String), + + /** + * Forces C40 encoding for data-matrix (type {@link Boolean}, or "true" or "false") {@link String } value). This + * option and {@link #DATA_MATRIX_COMPACT} are mutually exclusive. + */ + ForceC40(String), + + /** + * Specifies whether to use compact mode for Code-128 code (type {@link Boolean}, or "true" or "false" + * {@link String } value). + * This can yield slightly smaller bar codes. This option and {@link #FORCE_CODE_SET} are mutually + * exclusive. + */ + Code128Compact(String), +} + /* * Copyright 2007 ZXing authors * @@ -398,6 +551,91 @@ pub enum DecodeHintType { }*/ } +/** + * Callback which is invoked when a possible result point (significant + * point in the barcode image such as a corner) is found. + * + * @see DecodeHintType#NEED_RESULT_POINT_CALLBACK + */ +pub type RXingResultPointCallback = fn(&RXingResultPoint); + +pub enum DecodeHintValue { + /** + * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. + */ + Other(String), + + /** + * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + PureBarcode(bool), + + /** + * Image is known to be of one of a few possible formats. + * Maps to a {@link List} of {@link BarcodeFormat}s. + */ + PossibleFormats(BarcodeFormat), + + /** + * Spend more time to try to find a barcode; optimize for accuracy, not speed. + * Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + TryHarder(bool), + + /** + * Specifies what character encoding to use when decoding, where applicable (type String) + */ + CharacterSet(String), + + /** + * Allowed lengths of encoded data -- reject anything else. Maps to an {@code int[]}. + */ + AllowedLengths(Vec), + + /** + * Assume Code 39 codes employ a check digit. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + AssumeCode39CheckDigit(bool), + + /** + * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed. + * For example this affects FNC1 handling for Code 128 (aka GS1-128). Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + AssumeGs1(bool), + + /** + * If true, return the start and end digits in a Codabar barcode instead of stripping them. They + * are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them + * to not be. Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + ReturnCodabarStartEnd(bool), + + /** + * The caller needs to be notified via callback when a possible {@link RXingResultPoint} + * is found. Maps to a {@link RXingResultPointCallback}. + */ + NeedResultPointCallback(RXingResultPointCallback), + + /** + * Allowed extension lengths for EAN or UPC barcodes. Other formats will ignore this. + * Maps to an {@code int[]} of the allowed extension lengths, for example [2], [5], or [2, 5]. + * If it is optional to have an extension, do not set this hint. If this is set, + * and a UPC or EAN barcode is found but an extension is not, then no result will be returned + * at all. + */ + AllowedEanExtensions(Vec), + + /** + * If true, also tries to decode as inverted image. All configured decoders are simply called a + * second time with an inverted image. Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + AlsoInverted(bool), + // End of enumeration values. +} + /* * Copyright 2008 ZXing authors * @@ -448,12 +686,12 @@ pub trait Writer { * @return {@link BitMatrix} representing encoded barcode image * @throws WriterException if contents cannot be encoded legally in a format */ - fn encode_with_hints( + fn encode_with_hints( contents: &str, format: &BarcodeFormat, width: i32, height: i32, - hints: HashMap, + hints: &HashMap, ) -> Result; } @@ -475,8 +713,6 @@ pub trait Writer { //package com.google.zxing; - - /** * Implementations of this interface can decode an image of a barcode in some format into * the String it encodes. For example, {@link com.google.zxing.qrcode.QRCodeReader} can @@ -499,7 +735,7 @@ pub trait Reader { * @throws ChecksumException if a potential barcode is found but does not pass its checksum * @throws FormatException if a potential barcode is found but format is invalid */ - fn decode(image: BinaryBitmap) -> Result; + fn decode(image: &BinaryBitmap) -> Result; /** * Locates and decodes a barcode in some format within an image. This method also accepts @@ -515,9 +751,9 @@ pub trait Reader { * @throws ChecksumException if a potential barcode is found but does not pass its checksum * @throws FormatException if a potential barcode is found but format is invalid */ - fn decode_with_hints( - image: BinaryBitmap, - hints: HashMap, + fn decode_with_hints( + image: &BinaryBitmap, + hints: &HashMap, ) -> Result; /** @@ -631,6 +867,85 @@ pub enum RXingResultMetadataType { SYMBOLOGY_IDENTIFIER, } +pub enum RXingResultMetadataValue { + /** + * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. + */ + OTHER, + + /** + * Denotes the likely approximate orientation of the barcode in the image. This value + * is given as degrees rotated clockwise from the normal, upright orientation. + * For example a 1D barcode which was found by reading top-to-bottom would be + * said to have orientation "90". This key maps to an {@link Integer} whose + * value is in the range [0,360). + */ + Orientation(i32), + + /** + *

2D barcode formats typically encode text, but allow for a sort of 'byte mode' + * which is sometimes used to encode binary data. While {@link RXingResult} makes available + * the complete raw bytes in the barcode for these formats, it does not offer the bytes + * from the byte segments alone.

+ * + *

This maps to a {@link java.util.List} of byte arrays corresponding to the + * raw bytes in the byte segments in the barcode, in order.

+ */ + ByteSegments(Vec), + + /** + * Error correction level used, if applicable. The value type depends on the + * format, but is typically a String. + */ + ErrorCorrectionLevel(String), + + /** + * For some periodicals, indicates the issue number as an {@link Integer}. + */ + IssueNumber(i32), + + /** + * For some products, indicates the suggested retail price in the barcode as a + * formatted {@link String}. + */ + SuggestedPrice(String), + + /** + * For some products, the possible country of manufacture as a {@link String} denoting the + * ISO country code. Some map to multiple possible countries, like "US/CA". + */ + PossibleCountry(String), + + /** + * For some products, the extension text + */ + UpcEanExtension(String), + + /** + * PDF417-specific metadata + */ + Pdf417ExtraMetadata(String), + + /** + * If the code format supports structured append and the current scanned code is part of one then the + * sequence number is given with it. + */ + StructuredAppendSequence(u32), + + /** + * If the code format supports structured append and the current scanned code is part of one then the + * parity is given with it. + */ + StructuredAppendParity(u32), + + /** + * Barcode Symbology Identifier. + * Note: According to the GS1 specification the identifier may have to replace a leading FNC1/GS character + * when prepending to the barcode content. + */ + SymbologyIdentifier(String), +} + /* * Copyright 2007 ZXing authors * @@ -663,7 +978,7 @@ pub struct RXingResult { numBits: usize, resultPoints: Vec, format: BarcodeFormat, - resultMetadata: HashMap, + resultMetadata: HashMap, timestamp: u128, } impl RXingResult { @@ -758,15 +1073,24 @@ impl RXingResult { * {@code null}. This contains optional metadata about what was detected about the barcode, * like orientation. */ - pub fn getRXingResultMetadata(&self) -> &HashMap { + pub fn getRXingResultMetadata( + &self, + ) -> &HashMap { return &self.resultMetadata; } - pub fn putMetadata(&mut self, md_type: RXingResultMetadataType, value: String) { + pub fn putMetadata( + &mut self, + md_type: RXingResultMetadataType, + value: RXingResultMetadataValue, + ) { self.resultMetadata.insert(md_type, value); } - pub fn putAllMetadata(&mut self, metadata: HashMap) { + pub fn putAllMetadata( + &mut self, + metadata: HashMap, + ) { if self.resultMetadata.is_empty() { self.resultMetadata = metadata; } else { @@ -1248,34 +1572,6 @@ impl fmt::Display for BinaryBitmap { //package com.google.zxing; -/** - * Callback which is invoked when a possible result point (significant - * point in the barcode image such as a corner) is found. - * - * @see DecodeHintType#NEED_RESULT_POINT_CALLBACK - */ -pub trait RXingResultPointCallback { - fn foundPossibleRXingResultPoint(point: RXingResultPoint); -} - -/* - * Copyright 2009 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; - /** * The purpose of this class hierarchy is to abstract different bitmap implementations across * platforms into a standard interface for requesting greyscale luminance values. The interface @@ -1375,9 +1671,7 @@ pub trait LuminanceSource { * * @return A rotated version of this object. */ - fn rotateCounterClockwise( - &self, - ) -> Result, Exceptions> { + fn rotateCounterClockwise(&self) -> Result, Exceptions> { return Err(Exceptions::UnsupportedOperationException( "This luminance source does not support rotation by 90 degrees.".to_owned(), )); @@ -1389,9 +1683,7 @@ pub trait LuminanceSource { * * @return A rotated version of this object. */ - fn rotateCounterClockwise45( - &self, - ) -> Result, Exceptions> { + fn rotateCounterClockwise45(&self) -> Result, Exceptions> { return Err(Exceptions::UnsupportedOperationException( "This luminance source does not support rotation by 45 degrees.".to_owned(), )); @@ -1624,7 +1916,7 @@ impl PlanarYUVLuminanceSource { pub fn renderThumbnail(&self) -> Vec { let width = self.getWidth() / THUMBNAIL_SCALE_FACTOR; let height = self.getHeight() / THUMBNAIL_SCALE_FACTOR; - let mut pixels = vec![0;width * height]; + let mut pixels = vec![0; width * height]; let yuv = &self.yuv_data; let mut input_offset = self.top * self.data_width + self.left; @@ -1692,13 +1984,13 @@ impl LuminanceSource for PlanarYUVLuminanceSource { let offset = (y + self.top) * self.data_width + self.left; - let mut row = if row.len() >= width{ - row.to_vec()} - else{ - vec![0;width] - }; + let mut row = if row.len() >= width { + row.to_vec() + } else { + vec![0; width] + }; - row[..width].clone_from_slice(&self.yuv_data[offset..width+offset]); + row[..width].clone_from_slice(&self.yuv_data[offset..width + offset]); //System.arraycopy(yuvData, offset, row, 0, width); if self.invert { row = self.invert_block_of_bytes(row); @@ -1721,7 +2013,7 @@ impl LuminanceSource for PlanarYUVLuminanceSource { } let area = width * height; - let mut matrix = vec![0;area]; + let mut matrix = vec![0; area]; let mut inputOffset = self.top * self.data_width + self.left; // If the width matches the full width of the underlying data, perform a single copy. @@ -1738,7 +2030,8 @@ impl LuminanceSource for PlanarYUVLuminanceSource { for y in 0..height { //for (int y = 0; y < height; y++) { let outputOffset = y * width; - matrix[outputOffset..outputOffset+width].clone_from_slice(&self.yuv_data[inputOffset..inputOffset+width]); + matrix[outputOffset..outputOffset + width] + .clone_from_slice(&self.yuv_data[inputOffset..inputOffset + width]); //System.arraycopy(yuvData, inputOffset, matrix, outputOffset, width); inputOffset += self.data_width; } @@ -1841,12 +2134,12 @@ impl LuminanceSource for RGBLuminanceSource { let offset = (y + self.top) * self.dataWidth + self.left; let mut row = if row.len() >= width { - row.to_vec() - }else{ - vec![0;width] + row.to_vec() + } else { + vec![0; width] }; - row[..width].clone_from_slice(&self.luminances[offset..offset+width]); + row[..width].clone_from_slice(&self.luminances[offset..offset + width]); //System.arraycopy(self.luminances, offset, row, 0, width); if self.invert { row = self.invert_block_of_bytes(row); @@ -1869,12 +2162,12 @@ impl LuminanceSource for RGBLuminanceSource { } let area = width * height; - let mut matrix = vec![0;area]; + let mut matrix = vec![0; area]; let mut inputOffset = self.top * self.dataWidth + self.left; // If the width matches the full width of the underlying data, perform a single copy. if width == self.dataWidth { - matrix[..area].clone_from_slice(&self.luminances[inputOffset..area+inputOffset]); + matrix[..area].clone_from_slice(&self.luminances[inputOffset..area + inputOffset]); //System.arraycopy(self.luminances, inputOffset, matrix, 0, area); if self.invert { matrix = self.invert_block_of_bytes(matrix); @@ -1886,7 +2179,8 @@ impl LuminanceSource for RGBLuminanceSource { for y in 0..height { //for (int y = 0; y < height; y++) { let outputOffset = y * width; - matrix[outputOffset..width+outputOffset].clone_from_slice(&self.luminances[inputOffset..width+inputOffset]); + matrix[outputOffset..width + outputOffset] + .clone_from_slice(&self.luminances[inputOffset..width + inputOffset]); //System.arraycopy(luminances, inputOffset, matrix, outputOffset, width); inputOffset += self.dataWidth; } @@ -1949,11 +2243,11 @@ impl RGBLuminanceSource { // // Total number of pixels suffices, can ignore shape let size = width * height; - let mut luminances: Vec = vec![0;size]; + let mut luminances: Vec = vec![0; size]; for offset in 0..size { //for (int offset = 0; offset < size; offset++) { let pixel = pixels[offset]; - let r = (pixel >> 16) & 0xff ; // red + let r = (pixel >> 16) & 0xff; // red let g2 = (pixel >> 7) & 0x1fe; // 2 * green let b = pixel & 0xff; // blue // Calculate green-favouring average cheaply