diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 110bef0..b0a1756 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,8 +17,8 @@ jobs: steps: - uses: actions/checkout@v3 - name: Check - run: cargo check --release --verbose + run: cargo check --workspace --release --verbose - name: Build - run: cargo build --release --verbose + run: cargo build --workspace --release --verbose - name: Run tests - run: cargo test --release --verbose + run: cargo test --workspace --release --verbose diff --git a/Cargo.toml b/Cargo.toml index f79f94c..772c3d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,5 +65,6 @@ otsu_level = ["image"] [workspace] members = [ - "crates/one-d-proc-derive" + "crates/one-d-proc-derive", + "crates/cli" ] \ No newline at end of file diff --git a/crates/cli/.gitignore b/crates/cli/.gitignore new file mode 100644 index 0000000..ba1de84 --- /dev/null +++ b/crates/cli/.gitignore @@ -0,0 +1,25 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + + +# Added by cargo + +/target + +*.jpg +*.jpeg +*.png +*.tif +*.svg +*.webp +*.tiff +.DS_Store +/.vscode \ No newline at end of file diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 0000000..8cd3487 --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rxing-cli" +version = "0.1.11" +edition = "2021" +description = "A command line interface for rxing supporting encoding and decoding of multiple barcode formats" +license="Apache-2.0" +repository="https://github.com/rxing-core/rxing/tree/main/crates/cli" +keywords = ["barcode", "2d_barcode", "1d_barcode", "barcode_reader", "barcode_writer"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.1.1", features = ["derive"] } +rxing = {path = "../../", version = "~0.3.1", features = ["image", "svg_read", "svg_write"] } diff --git a/crates/cli/LICENSE b/crates/cli/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/crates/cli/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/crates/cli/README.md b/crates/cli/README.md new file mode 100644 index 0000000..2b2a970 --- /dev/null +++ b/crates/cli/README.md @@ -0,0 +1,19 @@ +# rxing-cli +A command line interface for rxing supporting encoding and decoding of barcode data. + +## Full documentation +`rxing-cli help` +`rxing-cli help encode` +`rxing-cli help decode` + +## Instalation +`cargo install rxing-cli` + +## Example Encode +`rxing-cli test_image.jpg encode --width 500 --height 500 --data "Sample Data and TEST Data" qrcode` + +## Example Decode +`rxing-cli test_image.jpg decode` + +## Example Multi Barcode Decode +`rxing-cli test_image.jpg decode --decode-multi` \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs new file mode 100644 index 0000000..345d77d --- /dev/null +++ b/crates/cli/src/main.rs @@ -0,0 +1,656 @@ +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; + +use clap::{ArgGroup, Parser, Subcommand}; +use rxing::{BarcodeFormat, MultiFormatWriter, Writer}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { + file_name: String, + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + #[command(group( + ArgGroup::new("advanced_display_group") + .required(false) + .args(["detailed_results","parsed_results","raw_bytes"]), + ))] + Decode { + /// Try much harder to detect barcodes. + #[arg(short, long)] + try_harder: bool, + + /// Search for multiple barcodes in an image instead of just one, this can be much slower. + #[arg(short, long)] + decode_multi: bool, + + /// Can be specified multiple times with different barcode formats, only listed formats are searched for. + #[arg(short, long, value_enum)] + barcode_types: Option>, + + /// Print detailed results data + #[arg(long)] + detailed_results: bool, + + /// Print parsed results (exclusive with --detailed-results and --raw-bytes) + #[arg(long)] + parsed_results: bool, + + /// Print raw bytes (exclusive with --detailed-results and --raw-bytes) + #[arg(long)] + raw_bytes: bool, + + /// Unspecified, application-specific hint. + #[arg(long)] + other: Option, + + /// Image is a pure monochrome image of a barcode. + #[arg(long)] + pure_barcode: Option, + + /// Specifies what character encoding to use when decoding, where applicable. + #[arg(long)] + character_set: Option, + + /// Allowed lengths of encoded data -- reject anything else.. + #[arg(long)] + allowed_lengths: Option>, + + /// Assume Code 39 codes employ a check digit. + #[arg(long)] + assume_code_39_check_digit: Option, + + /// 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). + #[arg(long, verbatim_doc_comment)] + assume_gs1: Option, + + /// 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. + #[arg(long, verbatim_doc_comment)] + return_codabar_start_end: Option, + + /// 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. + #[arg(long, verbatim_doc_comment)] + allowed_ean_extensions: Option>, + + /// If true, also tries to decode as inverted image. All configured decoders are simply called a + /// second time with an inverted image. + #[arg(long, verbatim_doc_comment)] + also_inverted: Option, + }, + #[command(group( + ArgGroup::new("code_set_rules") + .required(false) + .args(["code_128_compact", "force_code_set"]), + ))] + #[command(group( + ArgGroup::new("data_source") + .required(true) + .args(["data", "data_file"]), + ))] + #[command(group( + ArgGroup::new("data_matrix_encoding") + .required(false) + .args(["data_matrix_compact","force_c40"]), + ))] + Encode { + barcode_type: BarcodeFormat, + #[arg(long)] + width: u32, + #[arg(long)] + height: u32, + + /// String input for the encoder. + #[arg(short, long)] + data: Option, + + /// A file containing the text to be encoded. + #[arg(long)] + data_file: Option, + + /// 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 (L,M,Q,H). + /// For Aztec it is of type u32, representing the minimal percentage of error correction words. + /// For PDF417 it is of type u8, valid values being 0 to 8. + /// Note: an Aztec symbol should have a minimum of 25% EC words. + #[arg(long, verbatim_doc_comment)] + error_correction: Option, + + /// Specifies what character encoding to use where applicable. + #[arg(long)] + character_set: Option, + + /// Specifies whether to use compact mode for Data Matrix. + /// 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 #CHARACTER_SET encoding hint. + /// Compact encoding also provides GS1-FNC1 support when #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. + #[arg(long, verbatim_doc_comment)] + data_matrix_compact: Option, + + /// 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. + #[arg(long, verbatim_doc_comment)] + margin: Option, + + /** + Specifies whether to use compact mode for PDF417. + */ + #[arg(long)] + pdf_417_compact: Option, + + /** + Specifies what compaction mode to use for PDF417 + AUTO = 0, + TEXT = 1, + BYTE = 2, + NUMERIC = 3 + */ + #[arg(long)] + pdf_417_compaction: Option, + + /// Specifies whether to automatically insert ECIs when encoding PDF417. + /// 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 #CHARACTER_SET encoding hint. + #[arg(long, verbatim_doc_comment)] + pdf_417_auto_eci: Option, + + /// 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. + #[arg(long, verbatim_doc_comment)] + aztec_layers: Option, + + /** + Specifies the exact version of QR code to be encoded. + */ + #[arg(long)] + qr_version: Option, + + /// Specifies the QR code mask pattern to be used. Allowed values are + /// 0..8. By default the code will automatically select + /// the optimal mask pattern. + #[arg(long, verbatim_doc_comment)] + qr_mask_pattern: Option, + + /// Specifies whether to use compact mode for QR code. + /// 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 #CHARACTER_SET encoding hint. + #[arg(long, verbatim_doc_comment)] + qr_compact: Option, + + /** + Specifies whether the data should be encoded to the GS1 standard/ + */ + #[arg(long)] + gs1_format: Option, + + /// Forces which encoding will be used. Currently only used for Code-128 code sets. + /// Valid values are "A", "B", "C". + #[arg(long, verbatim_doc_comment)] + force_code_set: Option, + + /** + Forces C40 encoding for data-matrix. This + */ + #[arg(long)] + force_c40: Option, + + /** + Specifies whether to use compact mode for Code-128 code. + This can yield slightly smaller bar codes. + */ + #[arg(long)] + code_128_compact: Option, + }, +} + +fn main() { + let cli = Args::parse(); + match &cli.command { + Commands::Decode { + try_harder, + decode_multi, + barcode_types, + other, + pure_barcode, + character_set, + allowed_lengths, + assume_code_39_check_digit, + assume_gs1, + return_codabar_start_end, + allowed_ean_extensions, + also_inverted, + detailed_results, + parsed_results, + raw_bytes, + } => decode_command( + &cli.file_name, + try_harder, + decode_multi, + barcode_types, + other, + pure_barcode, + character_set, + allowed_lengths, + assume_code_39_check_digit, + assume_gs1, + return_codabar_start_end, + allowed_ean_extensions, + also_inverted, + detailed_results, + parsed_results, + raw_bytes, + ), + Commands::Encode { + barcode_type, + width, + height, + data, + data_file, + error_correction, + character_set, + data_matrix_compact, + margin, + pdf_417_compact, + pdf_417_compaction, + pdf_417_auto_eci, + aztec_layers, + qr_version, + qr_mask_pattern, + qr_compact, + gs1_format, + force_code_set, + force_c40, + code_128_compact, + } => encode_command( + &cli.file_name, + barcode_type, + width, + height, + data, + data_file, + error_correction, + character_set, + data_matrix_compact, + margin, + pdf_417_compact, + pdf_417_compaction, + pdf_417_auto_eci, + aztec_layers, + qr_version, + qr_mask_pattern, + qr_compact, + gs1_format, + force_code_set, + force_c40, + code_128_compact, + ), + } +} + +fn decode_command( + file_name: &str, + try_harder: &bool, + decode_multi: &bool, + barcode_types: &Option>, + other: &Option, + pure_barcode: &Option, + character_set: &Option, + allowed_lengths: &Option>, + assume_code_39_check_digit: &Option, + assume_gs1: &Option, + return_codabar_start_end: &Option, + allowed_ean_extensions: &Option>, + also_inverted: &Option, + detailed_result: &bool, + parsed_bytes: &bool, + raw_bytes: &bool, +) { + let mut hints: rxing::DecodingHintDictionary = HashMap::new(); + if let Some(other) = other { + hints.insert( + rxing::DecodeHintType::OTHER, + rxing::DecodeHintValue::Other(other.to_owned()), + ); + } + if let Some(pure_barcode) = pure_barcode { + hints.insert( + rxing::DecodeHintType::PURE_BARCODE, + rxing::DecodeHintValue::PureBarcode(*pure_barcode), + ); + } + if let Some(character_set) = character_set { + hints.insert( + rxing::DecodeHintType::CHARACTER_SET, + rxing::DecodeHintValue::CharacterSet(character_set.to_owned()), + ); + } + if let Some(allowed_lengths) = allowed_lengths { + hints.insert( + rxing::DecodeHintType::ALLOWED_LENGTHS, + rxing::DecodeHintValue::AllowedLengths(allowed_lengths.to_vec()), + ); + } + if let Some(assume_code_39_check_digit) = assume_code_39_check_digit { + hints.insert( + rxing::DecodeHintType::ASSUME_CODE_39_CHECK_DIGIT, + rxing::DecodeHintValue::AssumeCode39CheckDigit(*assume_code_39_check_digit), + ); + } + if let Some(assume_gs1) = assume_gs1 { + hints.insert( + rxing::DecodeHintType::ASSUME_GS1, + rxing::DecodeHintValue::AssumeGs1(*assume_gs1), + ); + } + if let Some(return_codabar_start_end) = return_codabar_start_end { + hints.insert( + rxing::DecodeHintType::RETURN_CODABAR_START_END, + rxing::DecodeHintValue::ReturnCodabarStartEnd(*return_codabar_start_end), + ); + } + if let Some(allowed_ean_extensions) = allowed_ean_extensions { + hints.insert( + rxing::DecodeHintType::ALLOWED_EAN_EXTENSIONS, + rxing::DecodeHintValue::AllowedEanExtensions(allowed_ean_extensions.to_vec()), + ); + } + if let Some(also_inverted) = also_inverted { + hints.insert( + rxing::DecodeHintType::ALSO_INVERTED, + rxing::DecodeHintValue::AlsoInverted(*also_inverted), + ); + } + + // println!( + // "Decode '{}' with: try_harder: {}, decode_multi: {}, barcode_types: {:?}", + // file_name, try_harder, decode_multi, barcode_types + // ); + + if !try_harder { + hints.insert( + rxing::DecodeHintType::TRY_HARDER, + rxing::DecodeHintValue::TryHarder(false), + ); + } + if let Some(barcode_type) = barcode_types { + hints.insert( + rxing::DecodeHintType::POSSIBLE_FORMATS, + rxing::DecodeHintValue::PossibleFormats(HashSet::from_iter( + barcode_type.iter().copied(), + )), + ); + } + + let path = PathBuf::from(file_name); + let extension = if let Some(ext) = path.extension() { + ext.to_string_lossy().to_string() + } else { + String::default() + }; + + if *decode_multi { + let results = if extension == "svg" { + rxing::helpers::detect_multiple_in_svg_with_hints(file_name, &mut hints) + } else { + rxing::helpers::detect_multiple_in_file_with_hints(file_name, &mut hints) + }; + match results { + Ok(result_array) => { + println!("Found {} results", result_array.len()); + for (i, result) in result_array.into_iter().enumerate() { + println!( + "Result {}:\n{}", + i, + print_result(&result, *detailed_result, *raw_bytes, *parsed_bytes) + ); + } + } + Err(search_err) => { + println!( + "Error while attempting to locate multiple barcodes in '{file_name}': {search_err}" + ); + } + } + } else { + let result = if extension == "svg" { + rxing::helpers::detect_in_svg_with_hints(file_name, None, &mut hints) + } else { + rxing::helpers::detect_in_file_with_hints(file_name, None, &mut hints) + }; + match result { + Ok(result) => { + println!( + "Detection result: \n{}", + print_result(&result, *detailed_result, *raw_bytes, *parsed_bytes) + ); + } + Err(search_err) => { + println!( + "Error while attempting to locate barcode in '{file_name}': {search_err}" + ); + } + } + } +} + +fn encode_command( + file_name: &str, + barcode_type: &BarcodeFormat, + width: &u32, + height: &u32, + data: &Option, + data_file: &Option, + error_correction: &Option, + character_set: &Option, + data_matrix_compact: &Option, + margin: &Option, + pdf_417_compact: &Option, + pdf_417_compaction: &Option, + pdf_417_auto_eci: &Option, + aztec_layers: &Option, + qr_version: &Option, + qr_mask_pattern: &Option, + qr_compact: &Option, + gs1_format: &Option, + force_code_set: &Option, + force_c40: &Option, + code_128_compact: &Option, +) { + // if data.is_none() && data_file.is_none() { + // println!("must provide either data string or data file"); + // return; + // } + // if data.is_some() && data_file.is_some() { + // println!("provide only data string or data file"); + // return; + // } + + let input_data = if let Some(path_from) = data_file { + if path_from.exists() { + let Ok(fl) = std::fs::File::open(path_from) else { + println!("file cannot be opened"); + return; + }; + std::io::read_to_string(fl).expect("file should read") + } else { + println!("{} does not exist", path_from.to_string_lossy()); + return; + } + } else if let Some(ds) = data { + ds.to_owned() + } else { + println!("Unknown error getting data"); + return; + }; + + let mut hints: rxing::EncodingHintDictionary = HashMap::new(); + + if let Some(ec) = error_correction { + hints.insert( + rxing::EncodeHintType::ERROR_CORRECTION, + rxing::EncodeHintValue::ErrorCorrection(ec.to_owned()), + ); + } + + if let Some(character_set) = character_set { + hints.insert( + rxing::EncodeHintType::CHARACTER_SET, + rxing::EncodeHintValue::CharacterSet(character_set.to_owned()), + ); + } + + if let Some(data_matrix_compact) = data_matrix_compact { + hints.insert( + rxing::EncodeHintType::DATA_MATRIX_COMPACT, + rxing::EncodeHintValue::DataMatrixCompact(*data_matrix_compact), + ); + } + + if let Some(margin) = margin { + hints.insert( + rxing::EncodeHintType::MARGIN, + rxing::EncodeHintValue::Margin(margin.to_owned()), + ); + } + + if let Some(pdf_417_compact) = pdf_417_compact { + hints.insert( + rxing::EncodeHintType::PDF417_COMPACT, + rxing::EncodeHintValue::Pdf417Compact(pdf_417_compact.to_string()), + ); + } + + if let Some(pdf_417_compaction) = pdf_417_compaction { + hints.insert( + rxing::EncodeHintType::PDF417_COMPACTION, + rxing::EncodeHintValue::Pdf417Compaction(pdf_417_compaction.to_owned()), + ); + } + + if let Some(pdf_417_auto_eci) = pdf_417_auto_eci { + hints.insert( + rxing::EncodeHintType::PDF417_AUTO_ECI, + rxing::EncodeHintValue::Pdf417AutoEci(pdf_417_auto_eci.to_string()), + ); + } + + if let Some(aztec_layers) = aztec_layers { + hints.insert( + rxing::EncodeHintType::AZTEC_LAYERS, + rxing::EncodeHintValue::AztecLayers(*aztec_layers), + ); + } + + if let Some(qr_version) = qr_version { + hints.insert( + rxing::EncodeHintType::QR_VERSION, + rxing::EncodeHintValue::QrVersion(qr_version.to_owned()), + ); + } + + if let Some(qr_mask_pattern) = qr_mask_pattern { + hints.insert( + rxing::EncodeHintType::QR_MASK_PATTERN, + rxing::EncodeHintValue::QrMaskPattern(qr_mask_pattern.to_owned()), + ); + } + + if let Some(qr_compact) = qr_compact { + println!("Warning, QRCompact can generate unreadable barcodes"); + hints.insert( + rxing::EncodeHintType::QR_COMPACT, + rxing::EncodeHintValue::QrCompact(qr_compact.to_string()), + ); + } + + if let Some(gs1_format) = gs1_format { + hints.insert( + rxing::EncodeHintType::GS1_FORMAT, + rxing::EncodeHintValue::Gs1Format(*gs1_format), + ); + } + + if let Some(force_code_set) = force_code_set { + hints.insert( + rxing::EncodeHintType::FORCE_CODE_SET, + rxing::EncodeHintValue::ForceCodeSet(force_code_set.to_owned()), + ); + } + + if let Some(force_c40) = force_c40 { + hints.insert( + rxing::EncodeHintType::FORCE_C40, + rxing::EncodeHintValue::ForceC40(*force_c40), + ); + } + + if let Some(code_128_compact) = code_128_compact { + hints.insert( + rxing::EncodeHintType::CODE128_COMPACT, + rxing::EncodeHintValue::Code128Compact(*code_128_compact), + ); + } + + // println!("Encode: file_name: {}, barcode_type: {}, width: {:?}, height: {:?}, data: '{:?}', data_file: {:?}", file_name, barcode_type, width, height, data, data_file); + + let writer = MultiFormatWriter::default(); + match writer.encode_with_hints( + &input_data, + barcode_type, + *width as i32, + *height as i32, + &hints, + ) { + Ok(result) => { + println!("Encode successful, saving..."); + match rxing::helpers::save_file(file_name, &result) { + Ok(_) => println!("Saved to '{file_name}'"), + Err(error) => println!("Could not save '{file_name}': {error}"), + } + } + Err(encode_error) => println!("Couldn't encode: {encode_error}"), + } +} + +fn print_result(result: &rxing::RXingResult, detailed: bool, raw: bool, parsed: bool) -> String { + let result_data = result.getText().escape_default().collect::(); + if detailed { + format!("[Barcode Format] {}\n[Metadata] {:?}\n[Points] {:?}\n[Number of Bits] {}\n[Timestamp] {}\n[Data] {}", result.getBarcodeFormat(),result.getRXingResultMetadata(), result.getRXingResultPoints(), result.getNumBits(), result.getTimestamp(), result_data) + } else if raw { + result + .getRawBytes() + .iter() + .fold(String::from(""), |acc, b| acc + " " + &b.to_string()) + } else if parsed { + let parsed_data = rxing::client::result::parseRXingResult(result); + parsed_data.to_string() + } else { + format!("({}) {}", result.getBarcodeFormat(), result_data) + } +}