From 07e8aa29716c7be5fbfe2661a788a529d9dababa Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 26 Dec 2022 13:29:57 -0600 Subject: [PATCH] port pdf 417 writer --- src/barcode_format.rs | 30 ++++ src/encode_hints.rs | 4 +- src/pdf417/PDF417Writer.java | 175 ---------------------- src/pdf417/encoder/compaction.rs | 22 +++ src/pdf417/mod.rs | 3 + src/pdf417/pdf_417_writer.rs | 243 +++++++++++++++++++++++++++++++ 6 files changed, 300 insertions(+), 177 deletions(-) delete mode 100644 src/pdf417/PDF417Writer.java create mode 100644 src/pdf417/pdf_417_writer.rs diff --git a/src/barcode_format.rs b/src/barcode_format.rs index c09186f..82c4e14 100644 --- a/src/barcode_format.rs +++ b/src/barcode_format.rs @@ -16,6 +16,8 @@ //package com.google.zxing; +use std::fmt::Display; + /** * Enumerates barcode formats known to this package. Please keep alphabetized. * @@ -74,3 +76,31 @@ pub enum BarcodeFormat { /** UPC/EAN extension format. Not a stand-alone format. */ UPC_EAN_EXTENSION, } + +impl Display for BarcodeFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + BarcodeFormat::AZTEC => "aztec", + BarcodeFormat::CODABAR => "codabar", + BarcodeFormat::CODE_39 => "code 39", + BarcodeFormat::CODE_93 => "code 93", + BarcodeFormat::CODE_128 => "code 128", + BarcodeFormat::DATA_MATRIX => "datamatrix", + BarcodeFormat::EAN_8 => "ean 8", + BarcodeFormat::EAN_13 => "ean 13", + BarcodeFormat::ITF => "itf", + BarcodeFormat::MAXICODE => "maxicode", + BarcodeFormat::PDF_417 => "pdf 417", + BarcodeFormat::QR_CODE => "qrcode", + BarcodeFormat::RSS_14 => "rss 14", + BarcodeFormat::RSS_EXPANDED => "rss expanded", + BarcodeFormat::UPC_A => "upc a", + BarcodeFormat::UPC_E => "upc e", + BarcodeFormat::UPC_EAN_EXTENSION => "upc/ean extension", + } + ) + } +} diff --git a/src/encode_hints.rs b/src/encode_hints.rs index 40d869f..5bc529a 100644 --- a/src/encode_hints.rs +++ b/src/encode_hints.rs @@ -16,7 +16,7 @@ //package com.google.zxing; -use crate::Dimension; +use crate::{pdf417::encoder::Dimensions, Dimension}; /** * These are a set of hints that you may pass to Writers to specify their behavior. @@ -255,7 +255,7 @@ pub enum EncodeHintValue { * Specifies the minimum and maximum number of rows and columns for PDF417 (type * {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}). */ - Pdf417Dimensions, + Pdf417Dimensions(Dimensions), /** * Specifies whether to automatically insert ECIs when encoding PDF417 (type {@link Boolean}, or "true" or "false" diff --git a/src/pdf417/PDF417Writer.java b/src/pdf417/PDF417Writer.java deleted file mode 100644 index 80e6ff7..0000000 --- a/src/pdf417/PDF417Writer.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2012 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.pdf417; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.Writer; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.pdf417.encoder.Compaction; -import com.google.zxing.pdf417.encoder.Dimensions; -import com.google.zxing.pdf417.encoder.PDF417; - -import java.nio.charset.Charset; -import java.util.Map; - -/** - * @author Jacob Haynes - * @author qwandor@google.com (Andrew Walbran) - */ -public final class PDF417Writer implements Writer { - - /** - * default white space (margin) around the code - */ - private static final int WHITE_SPACE = 30; - - /** - * default error correction level - */ - private static final int DEFAULT_ERROR_CORRECTION_LEVEL = 2; - - @Override - public BitMatrix encode(String contents, - BarcodeFormat format, - int width, - int height, - Map hints) throws WriterException { - if (format != BarcodeFormat.PDF_417) { - throw new IllegalArgumentException("Can only encode PDF_417, but got " + format); - } - - PDF417 encoder = new PDF417(); - int margin = WHITE_SPACE; - int errorCorrectionLevel = DEFAULT_ERROR_CORRECTION_LEVEL; - boolean autoECI = false; - - if (hints != null) { - if (hints.containsKey(EncodeHintType.PDF417_COMPACT)) { - encoder.setCompact(Boolean.parseBoolean(hints.get(EncodeHintType.PDF417_COMPACT).toString())); - } - if (hints.containsKey(EncodeHintType.PDF417_COMPACTION)) { - encoder.setCompaction(Compaction.valueOf(hints.get(EncodeHintType.PDF417_COMPACTION).toString())); - } - if (hints.containsKey(EncodeHintType.PDF417_DIMENSIONS)) { - Dimensions dimensions = (Dimensions) hints.get(EncodeHintType.PDF417_DIMENSIONS); - encoder.setDimensions(dimensions.getMaxCols(), - dimensions.getMinCols(), - dimensions.getMaxRows(), - dimensions.getMinRows()); - } - if (hints.containsKey(EncodeHintType.MARGIN)) { - margin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); - } - if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) { - errorCorrectionLevel = Integer.parseInt(hints.get(EncodeHintType.ERROR_CORRECTION).toString()); - } - if (hints.containsKey(EncodeHintType.CHARACTER_SET)) { - Charset encoding = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString()); - encoder.setEncoding(encoding); - } - autoECI = hints.containsKey(EncodeHintType.PDF417_AUTO_ECI) && - Boolean.parseBoolean(hints.get(EncodeHintType.PDF417_AUTO_ECI).toString()); - } - - return bitMatrixFromEncoder(encoder, contents, errorCorrectionLevel, width, height, margin, autoECI); - } - - @Override - public BitMatrix encode(String contents, - BarcodeFormat format, - int width, - int height) throws WriterException { - return encode(contents, format, width, height, null); - } - - /** - * Takes encoder, accounts for width/height, and retrieves bit matrix - */ - private static BitMatrix bitMatrixFromEncoder(PDF417 encoder, - String contents, - int errorCorrectionLevel, - int width, - int height, - int margin, - boolean autoECI) throws WriterException { - encoder.generateBarcodeLogic(contents, errorCorrectionLevel, autoECI); - - int aspectRatio = 4; - byte[][] originalScale = encoder.getBarcodeMatrix().getScaledMatrix(1, aspectRatio); - boolean rotated = false; - if ((height > width) != (originalScale[0].length < originalScale.length)) { - originalScale = rotateArray(originalScale); - rotated = true; - } - - int scaleX = width / originalScale[0].length; - int scaleY = height / originalScale.length; - int scale = Math.min(scaleX, scaleY); - - if (scale > 1) { - byte[][] scaledMatrix = - encoder.getBarcodeMatrix().getScaledMatrix(scale, scale * aspectRatio); - if (rotated) { - scaledMatrix = rotateArray(scaledMatrix); - } - return bitMatrixFromBitArray(scaledMatrix, margin); - } - return bitMatrixFromBitArray(originalScale, margin); - } - - /** - * This takes an array holding the values of the PDF 417 - * - * @param input a byte array of information with 0 is black, and 1 is white - * @param margin border around the barcode - * @return BitMatrix of the input - */ - private static BitMatrix bitMatrixFromBitArray(byte[][] input, int margin) { - // Creates the bit matrix with extra space for whitespace - BitMatrix output = new BitMatrix(input[0].length + 2 * margin, input.length + 2 * margin); - output.clear(); - for (int y = 0, yOutput = output.getHeight() - margin - 1; y < input.length; y++, yOutput--) { - byte[] inputY = input[y]; - for (int x = 0; x < input[0].length; x++) { - // Zero is white in the byte matrix - if (inputY[x] == 1) { - output.set(x + margin, yOutput); - } - } - } - return output; - } - - /** - * Takes and rotates the it 90 degrees - */ - private static byte[][] rotateArray(byte[][] bitarray) { - byte[][] temp = new byte[bitarray[0].length][bitarray.length]; - for (int ii = 0; ii < bitarray.length; ii++) { - // This makes the direction consistent on screen when rotating the - // screen; - int inverseii = bitarray.length - ii - 1; - for (int jj = 0; jj < bitarray[0].length; jj++) { - temp[jj][inverseii] = bitarray[ii][jj]; - } - } - return temp; - } - -} diff --git a/src/pdf417/encoder/compaction.rs b/src/pdf417/encoder/compaction.rs index 83855c5..941b4d7 100644 --- a/src/pdf417/encoder/compaction.rs +++ b/src/pdf417/encoder/compaction.rs @@ -14,6 +14,8 @@ * limitations under the License. */ +use crate::Exceptions; + /** * Represents possible PDF417 barcode compaction types. */ @@ -24,3 +26,23 @@ pub enum Compaction { BYTE = 2, NUMERIC = 3, } + +impl TryFrom<&String> for Compaction { + type Error = Exceptions; + + fn try_from(value: &String) -> Result { + if let Ok(num_val) = value.parse::() { + match num_val { + 0 => return Ok(Compaction::AUTO), + 1 => return Ok(Compaction::TEXT), + 2 => return Ok(Compaction::BYTE), + 3 => return Ok(Compaction::NUMERIC), + _ => {} + } + } + Err(Exceptions::FormatException(format!( + "Compaction must be 0-3 (inclusivie). Found: {}", + value + ))) + } +} diff --git a/src/pdf417/mod.rs b/src/pdf417/mod.rs index 836db32..fb1d90d 100644 --- a/src/pdf417/mod.rs +++ b/src/pdf417/mod.rs @@ -9,3 +9,6 @@ pub use pdf_417_result_metadata::*; mod pdf_417_reader; pub use pdf_417_reader::*; + +mod pdf_417_writer; +pub use pdf_417_writer::*; diff --git a/src/pdf417/pdf_417_writer.rs b/src/pdf417/pdf_417_writer.rs new file mode 100644 index 0000000..383b6d4 --- /dev/null +++ b/src/pdf417/pdf_417_writer.rs @@ -0,0 +1,243 @@ +/* + * Copyright 2012 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 crate::{ + common::BitMatrix, BarcodeFormat, EncodeHintType, EncodeHintValue, Exceptions, Writer, +}; + +use super::encoder::PDF417; + +/** + * default white space (margin) around the code + */ +const WHITE_SPACE: u32 = 30; + +/** + * default error correction level + */ +const DEFAULT_ERROR_CORRECTION_LEVEL: u32 = 2; + +/** + * @author Jacob Haynes + * @author qwandor@google.com (Andrew Walbran) + */ +pub struct PDF417Writer; + +impl Writer for PDF417Writer { + fn encode( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + ) -> Result { + self.encode_with_hints(contents, format, width, height, &HashMap::new()) + } + + fn encode_with_hints( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + hints: &crate::EncodingHintDictionary, + ) -> Result { + if format != &BarcodeFormat::PDF_417 { + return Err(Exceptions::IllegalArgumentException(format!( + "Can only encode PDF_417, but got {}", + format + ))); + } + + let mut encoder = PDF417::new(); + let mut margin = WHITE_SPACE; + let mut errorCorrectionLevel = DEFAULT_ERROR_CORRECTION_LEVEL; + let mut autoECI = false; + + if !hints.is_empty() { + if let Some(EncodeHintValue::Pdf417Compact(compact)) = + hints.get(&EncodeHintType::PDF417_COMPACT) + { + // if hints.containsKey(EncodeHintType::PDF417_COMPACT) { + if let Ok(res) = compact.parse::() { + encoder.setCompact(res); + } + // encoder.setCompact(Boolean.parseBoolean(hints.get(EncodeHintType::PDF417_COMPACT).toString())); + } + if let Some(EncodeHintValue::Pdf417Compaction(compaction)) = + hints.get(&EncodeHintType::PDF417_COMPACTION) + { + // if hints.containsKey(EncodeHintType::PDF417_COMPACTION) { + encoder.setCompaction(compaction.try_into()?); + } + if let Some(EncodeHintValue::Pdf417Dimensions(dimensions)) = + hints.get(&EncodeHintType::PDF417_DIMENSIONS) + { + // if hints.containsKey(EncodeHintType::PDF417_DIMENSIONS) { + // Dimensions dimensions = (Dimensions) hints.get(EncodeHintType::PDF417_DIMENSIONS); + encoder.setDimensions( + dimensions.getMaxCols() as u32, + dimensions.getMinCols() as u32, + dimensions.getMaxRows() as u32, + dimensions.getMinRows() as u32, + ); + } + if let Some(EncodeHintValue::Margin(m1)) = hints.get(&EncodeHintType::MARGIN) { + // if hints.containsKey(EncodeHintType::MARGIN) { + if let Ok(m) = m1.parse::() { + //margin = Integer.parseInt(hints.get(EncodeHintType::MARGIN).toString()); + margin = m; + } + } + if let Some(EncodeHintValue::ErrorCorrection(ec)) = + hints.get(&EncodeHintType::ERROR_CORRECTION) + { + // if hints.containsKey(EncodeHintType::ERROR_CORRECTION) { + if let Ok(ec_parsed) = ec.parse::() { + errorCorrectionLevel = ec_parsed; + } + // errorCorrectionLevel = Integer.parseInt(hints.get(EncodeHintType::ERROR_CORRECTION).toString()); + } + if let Some(EncodeHintValue::CharacterSet(cs)) = + hints.get(&EncodeHintType::CHARACTER_SET) + { + // if hints.containsKey(EncodeHintType::CHARACTER_SET) { + // if let Some(encoding::label::encoding_from_whatwg_label(cs)) + // let encoding = Charset.forName(hints.get(&EncodeHintType::CHARACTER_SET).toString()); + encoder.setEncoding(encoding::label::encoding_from_whatwg_label(cs)); + } + if let Some(EncodeHintValue::Pdf417AutoEci(auto_eci_str)) = + hints.get(&EncodeHintType::PDF417_AUTO_ECI) + { + if let Ok(auto_eci_parsed) = auto_eci_str.parse::() { + autoECI = auto_eci_parsed; + } + } + // autoECI = hints.containsKey(EncodeHintType::PDF417_AUTO_ECI) && + // Boolean.parseBoolean(hints.get(EncodeHintType::PDF417_AUTO_ECI).toString()); + } + + Self::bitMatrixFromEncoder( + &mut encoder, + contents, + errorCorrectionLevel, + width as u32, + height as u32, + margin, + autoECI, + ) + } +} + +impl PDF417Writer { + /** + * Takes encoder, accounts for width/height, and retrieves bit matrix + */ + fn bitMatrixFromEncoder( + encoder: &mut PDF417, + contents: &str, + errorCorrectionLevel: u32, + width: u32, + height: u32, + margin: u32, + autoECI: bool, + ) -> Result { + encoder.generateBarcodeLogicWithAutoECI(contents, errorCorrectionLevel, autoECI)?; + + let aspectRatio = 4; + let mut originalScale = encoder + .getBarcodeMatrix() + .as_ref() + .unwrap() + .getScaledMatrix(1, aspectRatio); + let mut rotated = false; + if (height > width) != (originalScale[0].len() < originalScale.len()) { + originalScale = Self::rotateArray(&originalScale); + rotated = true; + } + + let scaleX = width as usize / originalScale[0].len(); + let scaleY = height as usize / originalScale.len(); + let scale = scaleX.min(scaleY); + + if scale > 1 { + let mut scaledMatrix = encoder + .getBarcodeMatrix() + .as_ref() + .unwrap() + .getScaledMatrix(scale, scale * aspectRatio); + if rotated { + scaledMatrix = Self::rotateArray(&scaledMatrix); + } + return Ok(Self::bitMatrixFromBitArray(&scaledMatrix, margin)); + } + Ok(Self::bitMatrixFromBitArray(&originalScale, margin)) + } + + /** + * This takes an array holding the values of the PDF 417 + * + * @param input a byte array of information with 0 is black, and 1 is white + * @param margin border around the barcode + * @return BitMatrix of the input + */ + fn bitMatrixFromBitArray(input: &Vec>, margin: u32) -> BitMatrix { + // Creates the bit matrix with extra space for whitespace + let mut output = BitMatrix::new( + input[0].len() as u32 + 2 * margin, + input.len() as u32 + 2 * margin, + ) + .expect("must generate"); + output.clear(); + let mut y = 0; + let mut yOutput = output.getHeight() - margin - 1; + while y < input.len() { + // for (int y = 0, yOutput = output.getHeight() - margin - 1; y < input.length; y++, yOutput--) { + let inputY = &input[y]; + for x in 0..input[0].len() { + // for (int x = 0; x < input[0].length; x++) { + // Zero is white in the byte matrix + if inputY[x] == 1 { + output.set(x as u32 + margin, yOutput); + } + } + y += 1; + yOutput -= 1; + } + return output; + } + + /** + * Takes and rotates the it 90 degrees + */ + fn rotateArray(bitarray: &Vec>) -> Vec> { + let mut temp = vec![vec![0; bitarray[0].len()]; bitarray.len()]; // new byte[bitarray[0].length][bitarray.length]; + for ii in 0..bitarray.len() { + // for (int ii = 0; ii < bitarray.length; ii++) { + // This makes the direction consistent on screen when rotating the + // screen; + let inverseii = bitarray.len() - ii - 1; + for jj in 0..bitarray[0].len() { + // for (int jj = 0; jj < bitarray[0].length; jj++) { + temp[jj][inverseii] = bitarray[ii][jj]; + } + } + + temp + } +}