From 07769fe15488f901acfd366f33090b694cd43aef Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 27 Nov 2022 12:00:35 -0600 Subject: [PATCH] port writer and tests, but does not pass --- src/aztec/aztec_reader.rs | 12 +- src/datamatrix/data_matrix_reader.rs | 240 ++++++------ src/datamatrix/data_matrix_writer.rs | 506 ++++++++++++++++---------- src/datamatrix/encoder/symbol_info.rs | 4 +- src/datamatrix/mod.rs | 4 +- src/encode_hints.rs | 10 +- src/qrcode/encoder/EncoderTestCase.rs | 19 +- src/qrcode/encoder/encoder.rs | 6 +- 8 files changed, 471 insertions(+), 330 deletions(-) diff --git a/src/aztec/aztec_reader.rs b/src/aztec/aztec_reader.rs index ba4d20f..074e765 100644 --- a/src/aztec/aztec_reader.rs +++ b/src/aztec/aztec_reader.rs @@ -89,12 +89,14 @@ impl Reader for AztecReader { // } // } - if let Some(rpcb) = hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK) { - if let DecodeHintValue::NeedResultPointCallback(cb) = rpcb { - for point in points { - cb(point); - } + if let Some(DecodeHintValue::NeedResultPointCallback(cb)) = + 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( diff --git a/src/datamatrix/data_matrix_reader.rs b/src/datamatrix/data_matrix_reader.rs index 174885d..90463cc 100644 --- a/src/datamatrix/data_matrix_reader.rs +++ b/src/datamatrix/data_matrix_reader.rs @@ -16,14 +16,18 @@ use std::collections::HashMap; -use crate::{Reader, Exceptions, common::{BitMatrix, DetectorRXingResult}, RXingResultPoint, DecodeHintType, RXingResult, BarcodeFormat, RXingResultMetadataType, RXingResultMetadataValue}; +use crate::{ + common::{BitMatrix, DetectorRXingResult}, + BarcodeFormat, DecodeHintType, Exceptions, RXingResult, RXingResultMetadataType, + RXingResultMetadataValue, RXingResultPoint, Reader, +}; -use super::{detector::Detector, decoder::Decoder}; +use super::{decoder::Decoder, detector::Detector}; -use lazy_static::lazy_static; +use lazy_static::lazy_static; -lazy_static!{ - static ref decoder : Decoder = Decoder::new(); +lazy_static! { + static ref decoder: Decoder = Decoder::new(); } /** @@ -33,61 +37,79 @@ lazy_static!{ */ pub struct DataMatrixReader; - // private static final RXingResultPoint[] NO_POINTS = new RXingResultPoint[0]; +// private static final RXingResultPoint[] NO_POINTS = new RXingResultPoint[0]; - // private final Decoder decoder = new Decoder(); - - impl Reader for DataMatrixReader { +// private final Decoder decoder = new Decoder(); +impl Reader for DataMatrixReader { /** - * 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 - * @throws ChecksumException if error correction fails - */ - fn decode(&mut self, image: &crate::BinaryBitmap) -> Result { - self.decode_with_hints(image,&HashMap::new()) + * 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 + * @throws ChecksumException if error correction fails + */ + fn decode( + &mut self, + image: &crate::BinaryBitmap, + ) -> Result { + self.decode_with_hints(image, &HashMap::new()) } /** - * 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 - * @throws ChecksumException if error correction fails - */ + * 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 + * @throws ChecksumException if error correction fails + */ fn decode_with_hints( &mut self, image: &crate::BinaryBitmap, hints: &crate::DecodingHintDictionary, ) -> Result { - let decoderRXingResult; - let mut points = Vec::new(); - if hints.contains_key(&DecodeHintType::PURE_BARCODE) { - let bits = self.extractPureBits(image.getBlackMatrix())?; - decoderRXingResult = decoder.decode(&bits)?; - points.clear(); - } else { - let detectorRXingResult = Detector::new(image.getBlackMatrix().clone())?.detect()?; - decoderRXingResult = decoder.decode(detectorRXingResult.getBits())?; - points = detectorRXingResult.getPoints().clone(); - } - let mut result = RXingResult::new(decoderRXingResult.getText().clone(), decoderRXingResult.getRawBytes().clone(), points.clone(), - BarcodeFormat::DATA_MATRIX); - 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_string())); - } - result.putMetadata(RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, RXingResultMetadataValue::SymbologyIdentifier(format!("]d{}" , decoderRXingResult.getSymbologyModifier()))); - - Ok(result) + let decoderRXingResult; + let mut points = Vec::new(); + if hints.contains_key(&DecodeHintType::PURE_BARCODE) { + let bits = self.extractPureBits(image.getBlackMatrix())?; + decoderRXingResult = decoder.decode(&bits)?; + points.clear(); + } else { + let detectorRXingResult = Detector::new(image.getBlackMatrix().clone())?.detect()?; + decoderRXingResult = decoder.decode(detectorRXingResult.getBits())?; + points = detectorRXingResult.getPoints().clone(); + } + let mut result = RXingResult::new( + decoderRXingResult.getText().clone(), + decoderRXingResult.getRawBytes().clone(), + points.clone(), + BarcodeFormat::DATA_MATRIX, + ); + 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_string()), + ); + } + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier(format!( + "]d{}", + decoderRXingResult.getSymbologyModifier() + )), + ); + + Ok(result) } fn reset(&mut self) { @@ -95,79 +117,73 @@ pub struct DataMatrixReader; } } - - - - impl DataMatrixReader { - - /** - * This method detects a code in a "pure" image -- that is, pure monochrome image - * which contains only an unrotated, unskewed, image of a code, with some white border - * around it. This is a specialized method that works exceptionally fast in this special - * case. - */ - fn extractPureBits(&self, image:&BitMatrix) -> Result { - - let Some(leftTopBlack) = image.getTopLeftOnBit() else { +impl DataMatrixReader { + /** + * This method detects a code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + */ + fn extractPureBits(&self, image: &BitMatrix) -> Result { + let Some(leftTopBlack) = image.getTopLeftOnBit() else { return Err(Exceptions::NotFoundException("".to_owned())) }; - let Some(rightBottomBlack) = image.getBottomRightOnBit()else { + let Some(rightBottomBlack) = image.getBottomRightOnBit()else { return Err(Exceptions::NotFoundException("".to_owned())) }; - let moduleSize = Self::moduleSize(&leftTopBlack, image)?; + let moduleSize = Self::moduleSize(&leftTopBlack, image)?; - let mut top = leftTopBlack[1]; - let bottom = rightBottomBlack[1]; - let mut left = leftTopBlack[0]; - let right = rightBottomBlack[0]; + let mut top = leftTopBlack[1]; + let bottom = rightBottomBlack[1]; + let mut left = leftTopBlack[0]; + let right = rightBottomBlack[0]; - let matrixWidth = (right - left + 1) / moduleSize; - let matrixHeight = (bottom - top + 1) / moduleSize; - if matrixWidth <= 0 || matrixHeight <= 0 { - return Err(Exceptions::NotFoundException("".to_owned())) - // throw NotFoundException.getNotFoundInstance(); - } - - // Push in the "border" by half the module width so that we start - // sampling in the middle of the module. Just in case the image is a - // little off, this will help recover. - let nudge = moduleSize / 2; - top += nudge; - left += nudge; - - // Now just read off the bits - let mut bits = BitMatrix::new(matrixWidth, matrixHeight)?; - for y in 0..matrixHeight { - // for (int y = 0; y < matrixHeight; y++) { - let iOffset = top + y * moduleSize; - for x in 0..matrixWidth { - // for (int x = 0; x < matrixWidth; x++) { - if image.get(left + x * moduleSize, iOffset) { - bits.set(x, y); + let matrixWidth = (right - left + 1) / moduleSize; + let matrixHeight = (bottom - top + 1) / moduleSize; + if matrixWidth <= 0 || matrixHeight <= 0 { + return Err(Exceptions::NotFoundException("".to_owned())); + // throw NotFoundException.getNotFoundInstance(); } - } - } - Ok( bits) - } - fn moduleSize( leftTopBlack:&[u32], image:&BitMatrix) -> Result { - let width = image.getWidth(); - let mut x = leftTopBlack[0]; - let y = leftTopBlack[1]; - while x < width && image.get(x, y) { - x+=1; - } - if x == width { - return Err(Exceptions::NotFoundException("".to_owned())) + // Push in the "border" by half the module width so that we start + // sampling in the middle of the module. Just in case the image is a + // little off, this will help recover. + let nudge = moduleSize / 2; + top += nudge; + left += nudge; + + // Now just read off the bits + let mut bits = BitMatrix::new(matrixWidth, matrixHeight)?; + for y in 0..matrixHeight { + // for (int y = 0; y < matrixHeight; y++) { + let iOffset = top + y * moduleSize; + for x in 0..matrixWidth { + // for (int x = 0; x < matrixWidth; x++) { + if image.get(left + x * moduleSize, iOffset) { + bits.set(x, y); + } + } + } + Ok(bits) } - let moduleSize = x - leftTopBlack[0]; - if moduleSize == 0 { - return Err(Exceptions::NotFoundException("".to_owned())) - } - - Ok(moduleSize) - } + fn moduleSize(leftTopBlack: &[u32], image: &BitMatrix) -> Result { + let width = image.getWidth(); + let mut x = leftTopBlack[0]; + let y = leftTopBlack[1]; + while x < width && image.get(x, y) { + x += 1; + } + if x == width { + return Err(Exceptions::NotFoundException("".to_owned())); + } + let moduleSize = x - leftTopBlack[0]; + if moduleSize == 0 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + Ok(moduleSize) + } } diff --git a/src/datamatrix/data_matrix_writer.rs b/src/datamatrix/data_matrix_writer.rs index 0bc680c..66a7cce 100644 --- a/src/datamatrix/data_matrix_writer.rs +++ b/src/datamatrix/data_matrix_writer.rs @@ -14,23 +14,21 @@ * limitations under the License. */ -package com.google.zxing.datamatrix; +use std::collections::HashMap; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.Writer; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.datamatrix.encoder.DefaultPlacement; -import com.google.zxing.Dimension; -import com.google.zxing.datamatrix.encoder.ErrorCorrection; -import com.google.zxing.datamatrix.encoder.HighLevelEncoder; -import com.google.zxing.datamatrix.encoder.MinimalEncoder; -import com.google.zxing.datamatrix.encoder.SymbolInfo; -import com.google.zxing.datamatrix.encoder.SymbolShapeHint; -import com.google.zxing.qrcode.encoder.ByteMatrix; +use encoding::EncodingRef; -import java.util.Map; -import java.nio.charset.Charset; +use crate::{ + common::BitMatrix, qrcode::encoder::ByteMatrix, BarcodeFormat, EncodeHintType, EncodeHintValue, + Exceptions, Writer, +}; + +use super::encoder::{ + high_level_encoder, minimal_encoder, DefaultPlacement, SymbolInfo, SymbolInfoLookup, + SymbolShapeHint, +}; + +use super::encoder::error_correction; /** * This object renders a Data Matrix code as a BitMatrix 2D array of greyscale values. @@ -38,183 +36,319 @@ import java.nio.charset.Charset; * @author dswitkin@google.com (Daniel Switkin) * @author Guillaume Le Biller Added to zxing lib. */ -public final class DataMatrixWriter implements Writer { +pub struct DataMatrixWriter; - @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) { - - if (contents.isEmpty()) { - throw new IllegalArgumentException("Found empty contents"); +impl Writer for DataMatrixWriter { + fn encode( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + ) -> Result { + self.encode_with_hints(contents, format, width, height, &HashMap::new()) } - if (format != BarcodeFormat.DATA_MATRIX) { - throw new IllegalArgumentException("Can only encode DATA_MATRIX, but got " + format); - } - - if (width < 0 || height < 0) { - throw new IllegalArgumentException("Requested dimensions can't be negative: " + width + 'x' + height); - } - - // Try to get force shape & min / max size - SymbolShapeHint shape = SymbolShapeHint.FORCE_NONE; - Dimension minSize = null; - Dimension maxSize = null; - if (hints != null) { - SymbolShapeHint requestedShape = (SymbolShapeHint) hints.get(EncodeHintType.DATA_MATRIX_SHAPE); - if (requestedShape != null) { - shape = requestedShape; - } - @SuppressWarnings("deprecation") - Dimension requestedMinSize = (Dimension) hints.get(EncodeHintType.MIN_SIZE); - if (requestedMinSize != null) { - minSize = requestedMinSize; - } - @SuppressWarnings("deprecation") - Dimension requestedMaxSize = (Dimension) hints.get(EncodeHintType.MAX_SIZE); - if (requestedMaxSize != null) { - maxSize = requestedMaxSize; - } - } - - - //1. step: Data encodation - String encoded; - - boolean hasCompactionHint = hints != null && hints.containsKey(EncodeHintType.DATA_MATRIX_COMPACT) && - Boolean.parseBoolean(hints.get(EncodeHintType.DATA_MATRIX_COMPACT).toString()); - if (hasCompactionHint) { - - boolean hasGS1FormatHint = hints.containsKey(EncodeHintType.GS1_FORMAT) && - Boolean.parseBoolean(hints.get(EncodeHintType.GS1_FORMAT).toString()); - - Charset charset = null; - boolean hasEncodingHint = hints.containsKey(EncodeHintType.CHARACTER_SET); - if (hasEncodingHint) { - charset = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString()); - } - encoded = MinimalEncoder.encodeHighLevel(contents, charset, hasGS1FormatHint ? 0x1D : -1, shape); - } else { - boolean hasForceC40Hint = hints != null && hints.containsKey(EncodeHintType.FORCE_C40) && - Boolean.parseBoolean(hints.get(EncodeHintType.FORCE_C40).toString()); - encoded = HighLevelEncoder.encodeHighLevel(contents, shape, minSize, maxSize, hasForceC40Hint); - } - - SymbolInfo symbolInfo = SymbolInfo.lookup(encoded.length(), shape, minSize, maxSize, true); - - //2. step: ECC generation - String codewords = ErrorCorrection.encodeECC200(encoded, symbolInfo); - - //3. step: Module placement in Matrix - DefaultPlacement placement = - new DefaultPlacement(codewords, symbolInfo.getSymbolDataWidth(), symbolInfo.getSymbolDataHeight()); - placement.place(); - - //4. step: low-level encoding - return encodeLowLevel(placement, symbolInfo, width, height); - } - - /** - * Encode the given symbol info to a bit matrix. - * - * @param placement The DataMatrix placement. - * @param symbolInfo The symbol info to encode. - * @return The bit matrix generated. - */ - private static BitMatrix encodeLowLevel(DefaultPlacement placement, SymbolInfo symbolInfo, int width, int height) { - int symbolWidth = symbolInfo.getSymbolDataWidth(); - int symbolHeight = symbolInfo.getSymbolDataHeight(); - - ByteMatrix matrix = new ByteMatrix(symbolInfo.getSymbolWidth(), symbolInfo.getSymbolHeight()); - - int matrixY = 0; - - for (int y = 0; y < symbolHeight; y++) { - // Fill the top edge with alternate 0 / 1 - int matrixX; - if ((y % symbolInfo.matrixHeight) == 0) { - matrixX = 0; - for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) { - matrix.set(matrixX, matrixY, (x % 2) == 0); - matrixX++; + fn encode_with_hints( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + hints: &crate::EncodingHintDictionary, + ) -> Result { + if contents.is_empty() { + return Err(Exceptions::IllegalArgumentException( + "Found empty contents".to_owned(), + )); } - matrixY++; - } - matrixX = 0; - for (int x = 0; x < symbolWidth; x++) { - // Fill the right edge with full 1 - if ((x % symbolInfo.matrixWidth) == 0) { - matrix.set(matrixX, matrixY, true); - matrixX++; + + if format != &BarcodeFormat::DATA_MATRIX { + return Err(Exceptions::IllegalArgumentException(format!( + "Can only encode DATA_MATRIX, but got {:?}", + format + ))); } - matrix.set(matrixX, matrixY, placement.getBit(x, y)); - matrixX++; - // Fill the right edge with alternate 0 / 1 - if ((x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1) { - matrix.set(matrixX, matrixY, (y % 2) == 0); - matrixX++; + + if width < 0 || height < 0 { + return Err(Exceptions::IllegalArgumentException(format!( + "Requested dimensions can't be negative: {}x{}", + width, height + ))); } - } - matrixY++; - // Fill the bottom edge with full 1 - if ((y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1) { - matrixX = 0; - for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) { - matrix.set(matrixX, matrixY, true); - matrixX++; + + // Try to get force shape & min / max size + let mut shape = &SymbolShapeHint::FORCE_NONE; + let mut minSize = None; + let mut maxSize = None; + if !hints.is_empty() { + if let Some(EncodeHintValue::DataMatrixShape(rq)) = + hints.get(&EncodeHintType::DATA_MATRIX_SHAPE) + { + shape = rq; + } + // @SuppressWarnings("deprecation") + let requestedMinSize = hints.get(&EncodeHintType::MIN_SIZE); + if let Some(EncodeHintValue::MinSize(rq)) = requestedMinSize { + minSize = Some(*rq); + } + // @SuppressWarnings("deprecation") + let requestedMaxSize = hints.get(&EncodeHintType::MAX_SIZE); + if let Some(EncodeHintValue::MinSize(rq)) = requestedMaxSize { + maxSize = Some(*rq); + } } - matrixY++; - } + + //1. step: Data encodation + let encoded; + + let hasCompactionHint = if let Some(EncodeHintValue::DataMatrixCompact(res)) = + hints.get(&EncodeHintType::DATA_MATRIX_COMPACT) + { + *res + } else { + false + }; + if hasCompactionHint { + let hasGS1FormatHint = if let Some(EncodeHintValue::Gs1Format(res)) = + hints.get(&EncodeHintType::GS1_FORMAT) + { + *res + } else { + false + }; + + let mut charset: Option = None; + let hasEncodingHint = hints.contains_key(&EncodeHintType::CHARACTER_SET); + if hasEncodingHint { + let Some(EncodeHintValue::CharacterSet(char_set_name)) = + hints.get(&EncodeHintType::CHARACTER_SET) else { + return Err(Exceptions::IllegalArgumentException("charset does not exist".to_owned())) + }; + charset = encoding::label::encoding_from_whatwg_label(char_set_name); + // charset = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString()); + } + encoded = minimal_encoder::encodeHighLevelWithDetails( + contents, + charset, + if hasGS1FormatHint { + Some(0x1D as char) + } else { + None + }, + *shape, + )?; + } else { + let hasForceC40Hint = if let Some(EncodeHintValue::ForceC40(hint)) = + hints.get(&EncodeHintType::FORCE_C40) + { + *hint + } else { + false + }; + encoded = high_level_encoder::encodeHighLevelWithDimensionForceC40( + contents, + *shape, + minSize, + maxSize, + hasForceC40Hint, + )?; + } + + let symbol_lookup = SymbolInfoLookup::new(); + let Some(symbolInfo) = symbol_lookup.lookup_with_codewords_shape_size_fail(encoded.len() as u32, *shape, &minSize, &maxSize, true)? else { + return Err(Exceptions::NotFoundException("symbol info is bad".to_owned())) + }; + + //2. step: ECC generation + let codewords = error_correction::encodeECC200(&encoded, symbolInfo)?; + + //3. step: Module placement in Matrix + let mut placement = DefaultPlacement::new( + codewords, + symbolInfo.getSymbolDataWidth()? as usize, + symbolInfo.getSymbolDataHeight()? as usize, + ); + placement.place(); + + //4. step: low-level encoding + Self::encodeLowLevel( + &placement, + symbolInfo, + width.try_into().unwrap(), + height.try_into().unwrap(), + ) } - - return convertByteMatrixToBitMatrix(matrix, width, height); - } - - /** - * Convert the ByteMatrix to BitMatrix. - * - * @param reqHeight The requested height of the image (in pixels) with the Datamatrix code - * @param reqWidth The requested width of the image (in pixels) with the Datamatrix code - * @param matrix The input matrix. - * @return The output matrix. - */ - private static BitMatrix convertByteMatrixToBitMatrix(ByteMatrix matrix, int reqWidth, int reqHeight) { - int matrixWidth = matrix.getWidth(); - int matrixHeight = matrix.getHeight(); - int outputWidth = Math.max(reqWidth, matrixWidth); - int outputHeight = Math.max(reqHeight, matrixHeight); - - int multiple = Math.min(outputWidth / matrixWidth, outputHeight / matrixHeight); - - int leftPadding = (outputWidth - (matrixWidth * multiple)) / 2 ; - int topPadding = (outputHeight - (matrixHeight * multiple)) / 2 ; - - BitMatrix output; - - // remove padding if requested width and height are too small - if (reqHeight < matrixHeight || reqWidth < matrixWidth) { - leftPadding = 0; - topPadding = 0; - output = new BitMatrix(matrixWidth, matrixHeight); - } else { - output = new BitMatrix(reqWidth, reqHeight); - } - - output.clear(); - for (int inputY = 0, outputY = topPadding; inputY < matrixHeight; inputY++, outputY += multiple) { - // Write the contents of this row of the bytematrix - for (int inputX = 0, outputX = leftPadding; inputX < matrixWidth; inputX++, outputX += multiple) { - if (matrix.get(inputX, inputY) == 1) { - output.setRegion(outputX, outputY, multiple, multiple); - } - } - } - - return output; - } - } + +impl DataMatrixWriter { + /** + * Encode the given symbol info to a bit matrix. + * + * @param placement The DataMatrix placement. + * @param symbolInfo The symbol info to encode. + * @return The bit matrix generated. + */ + fn encodeLowLevel( + placement: &DefaultPlacement, + symbolInfo: &SymbolInfo, + width: u32, + height: u32, + ) -> Result { + let symbolWidth = symbolInfo.getSymbolDataWidth()?; + let symbolHeight = symbolInfo.getSymbolDataHeight()?; + + let mut matrix = + ByteMatrix::new(symbolInfo.getSymbolWidth()?, symbolInfo.getSymbolHeight()?); + + let mut matrixY = 0; + + for y in 0..symbolHeight { + // for (int y = 0; y < symbolHeight; y++) { + // Fill the top edge with alternate 0 / 1 + let mut matrixX; + if (y % symbolInfo.matrixHeight) == 0 { + matrixX = 0; + for x in 0..symbolInfo.getSymbolWidth()? { + // for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) { + matrix.set_bool(matrixX, matrixY, (x % 2) == 0); + matrixX += 1; + } + matrixY += 1; + } + matrixX = 0; + for x in 0..symbolWidth { + // for (int x = 0; x < symbolWidth; x++) { + // Fill the right edge with full 1 + if (x % symbolInfo.matrixWidth) == 0 { + matrix.set_bool(matrixX, matrixY, true); + matrixX += 1; + } + matrix.set_bool(matrixX, matrixY, placement.getBit(x as usize, y as usize)); + matrixX += 1; + // Fill the right edge with alternate 0 / 1 + if (x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1 { + matrix.set_bool(matrixX, matrixY, (y % 2) == 0); + matrixX += 1; + } + } + matrixY += 1; + // Fill the bottom edge with full 1 + if (y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1 { + matrixX = 0; + for _x in 0..symbolInfo.getSymbolWidth()? { + // for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) { + matrix.set_bool(matrixX, matrixY, true); + matrixX += 1; + } + matrixY += 1; + } + } + + Self::convertByteMatrixToBitMatrix(&matrix, width, height) + } + + /** + * Convert the ByteMatrix to BitMatrix. + * + * @param reqHeight The requested height of the image (in pixels) with the Datamatrix code + * @param reqWidth The requested width of the image (in pixels) with the Datamatrix code + * @param matrix The input matrix. + * @return The output matrix. + */ + fn convertByteMatrixToBitMatrix( + matrix: &ByteMatrix, + reqWidth: u32, + reqHeight: u32, + ) -> Result { + let matrixWidth = matrix.getWidth(); + let matrixHeight = matrix.getHeight(); + let outputWidth = reqWidth.max(matrixWidth); + let outputHeight = reqHeight.max(matrixHeight); + + let multiple = (outputWidth / matrixWidth).min(outputHeight / matrixHeight); + + let mut leftPadding = (outputWidth - (matrixWidth * multiple)) / 2; + let mut topPadding = (outputHeight - (matrixHeight * multiple)) / 2; + + let mut output; + + // remove padding if requested width and height are too small + if reqHeight < matrixHeight || reqWidth < matrixWidth { + leftPadding = 0; + topPadding = 0; + output = BitMatrix::new(matrixWidth, matrixHeight)?; + } else { + output = BitMatrix::new(reqWidth, reqHeight)?; + } + + output.clear(); + let mut inputY = 0; + let mut outputY = topPadding; + while inputY < matrixHeight { + // for (int inputY = 0, outputY = topPadding; inputY < matrixHeight; inputY++, outputY += multiple) { + // Write the contents of this row of the bytematrix + let mut inputX = 0; + let mut outputX = leftPadding; + while inputX < matrixWidth { + // for (int inputX = 0, outputX = leftPadding; inputX < matrixWidth; inputX++, outputX += multiple) { + if matrix.get(inputX, inputY) == 1 { + output.setRegion(outputX, outputY, multiple, multiple)?; + } + + inputX += 1; + outputX += multiple + } + inputY += 1; + outputY += multiple + } + + Ok(output) + } +} + +#[cfg(test)] +mod tests{ + use std::collections::HashMap; + + use crate::{EncodeHintType, BarcodeFormat, datamatrix::{encoder::SymbolShapeHint, DataMatrixWriter}, Writer, EncodeHintValue}; + + + #[test] + fn testDataMatrixImageWriter() { + + let mut hints = HashMap::new(); + hints.insert(EncodeHintType::DATA_MATRIX_SHAPE, EncodeHintValue::DataMatrixShape(SymbolShapeHint::FORCE_SQUARE)); + + let bigEnough = 64; + let writer = DataMatrixWriter{}; + let matrix = writer.encode_with_hints("Hello Google", &BarcodeFormat::DATA_MATRIX, bigEnough, bigEnough, &hints).expect("must encode"); + assert!(bigEnough >= matrix.getWidth() as i32); + assert!(bigEnough >= matrix.getHeight() as i32); + } + + #[test] + fn testDataMatrixWriter() { + + let mut hints = HashMap::new(); + hints.insert(EncodeHintType::DATA_MATRIX_SHAPE, EncodeHintValue::DataMatrixShape(SymbolShapeHint::FORCE_SQUARE)); + + let bigEnough = 14; + let writer = DataMatrixWriter{}; + let matrix = writer.encode_with_hints("Hello Me", &BarcodeFormat::DATA_MATRIX, bigEnough, bigEnough, &hints).expect("must encode"); + assert_eq!(bigEnough, matrix.getWidth() as i32); + assert_eq!(bigEnough, matrix.getHeight() as i32); + } + + #[test] + fn testDataMatrixTooSmall() { + // The DataMatrix will not fit in this size, so the matrix should come back bigger + let tooSmall = 8; + let writer = DataMatrixWriter{}; + let matrix = writer.encode_with_hints("http://www.google.com/", &BarcodeFormat::DATA_MATRIX, tooSmall, tooSmall, &HashMap::new()).expect("must encode"); + + assert!(tooSmall < matrix.getWidth() as i32); + assert!(tooSmall < matrix.getHeight() as i32); + } + +} \ No newline at end of file diff --git a/src/datamatrix/encoder/symbol_info.rs b/src/datamatrix/encoder/symbol_info.rs index bd2475a..b3a2fbb 100644 --- a/src/datamatrix/encoder/symbol_info.rs +++ b/src/datamatrix/encoder/symbol_info.rs @@ -65,8 +65,8 @@ pub struct SymbolInfo { rectangular: bool, dataCapacity: u32, errorCodewords: u32, - matrixWidth: u32, - matrixHeight: u32, + pub(crate) matrixWidth: u32, + pub(crate) matrixHeight: u32, dataRegions: u32, rsBlockData: i32, rsBlockError: u32, diff --git a/src/datamatrix/mod.rs b/src/datamatrix/mod.rs index 61fff3b..5a7186b 100644 --- a/src/datamatrix/mod.rs +++ b/src/datamatrix/mod.rs @@ -3,4 +3,6 @@ pub mod detector; pub mod encoder; mod data_matrix_reader; -pub use data_matrix_reader::*; \ No newline at end of file +mod data_matrix_writer; +pub use data_matrix_reader::*; +pub use data_matrix_writer::*; diff --git a/src/encode_hints.rs b/src/encode_hints.rs index 4b3fa56..9de767f 100644 --- a/src/encode_hints.rs +++ b/src/encode_hints.rs @@ -196,7 +196,7 @@ pub enum EncodeHintValue { /** * Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint}) */ - DataMatrixShape, + DataMatrixShape(crate::datamatrix::encoder::SymbolShapeHint), /** * Specifies whether to use compact mode for Data Matrix (type {@link Boolean}, or "true" or "false" @@ -212,7 +212,7 @@ pub enum EncodeHintValue { * for the purpose of delimiting AIs. * This option and {@link #FORCE_C40} are mutually exclusive. */ - DataMatrixCompact(String), + DataMatrixCompact(bool), /** * Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now. @@ -221,7 +221,7 @@ pub enum EncodeHintValue { * {@link com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int)} */ #[deprecated] - MinSize, + MinSize(Dimension), /** * Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now. @@ -304,7 +304,7 @@ pub enum EncodeHintValue { * Specifies whether the data should be encoded to the GS1 standard (type {@link Boolean}, or "true" or "false" * {@link String } value). */ - Gs1Format(String), + Gs1Format(bool), /** * Forces which encoding will be used. Currently only used for Code-128 code sets (Type {@link String}). @@ -317,7 +317,7 @@ pub enum EncodeHintValue { * 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), + ForceC40(bool), /** * Specifies whether to use compact mode for Code-128 code (type {@link Boolean}, or "true" or "false" diff --git a/src/qrcode/encoder/EncoderTestCase.rs b/src/qrcode/encoder/EncoderTestCase.rs index 95b3faa..e808197 100644 --- a/src/qrcode/encoder/EncoderTestCase.rs +++ b/src/qrcode/encoder/EncoderTestCase.rs @@ -305,10 +305,7 @@ fn testEncodeShiftjisNumeric() { fn testEncodeGS1WithStringTypeHint() { let mut hints = HashMap::new(); - hints.insert( - EncodeHintType::GS1_FORMAT, - EncodeHintValue::Gs1Format("true".to_owned()), - ); + hints.insert(EncodeHintType::GS1_FORMAT, EncodeHintValue::Gs1Format(true)); let qrCode = encoder::encode_with_hints("100001%11171218", ErrorCorrectionLevel::H, &hints) .expect("encode"); verifyGS1EncodedData(&qrCode); @@ -318,10 +315,7 @@ fn testEncodeGS1WithStringTypeHint() { fn testEncodeGS1WithBooleanTypeHint() { let mut hints = HashMap::new(); - hints.insert( - EncodeHintType::GS1_FORMAT, - EncodeHintValue::Gs1Format("true".to_owned()), - ); + hints.insert(EncodeHintType::GS1_FORMAT, EncodeHintValue::Gs1Format(true)); let qrCode = encoder::encode_with_hints("100001%11171218", ErrorCorrectionLevel::H, &hints) .expect("encode"); verifyGS1EncodedData(&qrCode); @@ -333,7 +327,7 @@ fn testDoesNotEncodeGS1WhenBooleanTypeHintExplicitlyFalse() { hints.insert( EncodeHintType::GS1_FORMAT, - EncodeHintValue::Gs1Format("false".to_owned()), + EncodeHintValue::Gs1Format(false), ); let qrCode = @@ -347,7 +341,7 @@ fn testDoesNotEncodeGS1WhenStringTypeHintExplicitlyFalse() { hints.insert( EncodeHintType::GS1_FORMAT, - EncodeHintValue::Gs1Format("false".to_owned()), + EncodeHintValue::Gs1Format(false), ); let qrCode = encoder::encode_with_hints("ABCDEF", ErrorCorrectionLevel::H, &hints).expect("encode"); @@ -362,10 +356,7 @@ fn testGS1ModeHeaderWithECI() { EncodeHintType::CHARACTER_SET, EncodeHintValue::CharacterSet("utf8".to_owned()), ); - hints.insert( - EncodeHintType::GS1_FORMAT, - EncodeHintValue::Gs1Format("true".to_owned()), - ); + hints.insert(EncodeHintType::GS1_FORMAT, EncodeHintValue::Gs1Format(true)); let qrCode = encoder::encode_with_hints("hello", ErrorCorrectionLevel::H, &hints).expect("encode"); let expected = r"<< diff --git a/src/qrcode/encoder/encoder.rs b/src/qrcode/encoder/encoder.rs index 8b42cc4..f3e4377 100644 --- a/src/qrcode/encoder/encoder.rs +++ b/src/qrcode/encoder/encoder.rs @@ -104,11 +104,7 @@ pub fn encode_with_hints( let has_gs1_format_hint = hints.contains_key(&EncodeHintType::GS1_FORMAT) && if let EncodeHintValue::Gs1Format(v) = hints.get(&EncodeHintType::GS1_FORMAT).unwrap() { - if let Ok(vb) = v.parse::() { - vb - } else { - false - } + *v } else { false };