mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 12:22:34 +00:00
662 lines
26 KiB
Rust
662 lines
26 KiB
Rust
/*
|
||
* 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::oned;
|
||
|
||
/**
|
||
* This object renders a CODE128 code as a {@link BitMatrix}.
|
||
*
|
||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||
*/
|
||
|
||
const CODE_START_A: i32 = 103;
|
||
|
||
const CODE_START_B: i32 = 104;
|
||
|
||
const CODE_START_C: i32 = 105;
|
||
|
||
const CODE_CODE_A: i32 = 101;
|
||
|
||
const CODE_CODE_B: i32 = 100;
|
||
|
||
const CODE_CODE_C: i32 = 99;
|
||
|
||
const CODE_STOP: i32 = 106;
|
||
|
||
// Dummy characters used to specify control characters in input
|
||
const ESCAPE_FNC_1: char = 'ñ';
|
||
|
||
const ESCAPE_FNC_2: char = 'ò';
|
||
|
||
const ESCAPE_FNC_3: char = 'ó';
|
||
|
||
const ESCAPE_FNC_4: char = 'ô';
|
||
|
||
// Code A, Code B, Code C
|
||
const CODE_FNC_1: i32 = 102;
|
||
|
||
// Code A, Code B
|
||
const CODE_FNC_2: i32 = 97;
|
||
|
||
// Code A, Code B
|
||
const CODE_FNC_3: i32 = 96;
|
||
|
||
// Code A
|
||
const CODE_FNC_4_A: i32 = 101;
|
||
|
||
// Code B
|
||
const CODE_FNC_4_B: i32 = 100;
|
||
pub struct Code128Writer {
|
||
super: OneDimensionalCodeWriter;
|
||
}
|
||
|
||
impl Code128Writer {
|
||
|
||
// Results of minimal lookahead for code C
|
||
enum CType {
|
||
|
||
UNCODABLE(), ONE_DIGIT(), TWO_DIGITS(), FNC_1()
|
||
}
|
||
|
||
pub fn get_supported_write_formats(&self) -> Collection<BarcodeFormat> {
|
||
return Collections::singleton(BarcodeFormat::CODE_128);
|
||
}
|
||
|
||
pub fn encode(&self, contents: &String) -> Vec<bool> {
|
||
return self.encode(&contents, null);
|
||
}
|
||
|
||
pub fn encode(&self, contents: &String, hints: &Map<EncodeHintType, ?>) -> Vec<bool> {
|
||
let forced_code_set: i32 = ::check(&contents, &hints);
|
||
let has_compaction_hint: bool = hints != null && hints.contains_key(EncodeHintType::CODE128_COMPACT) && Boolean::parse_boolean(&hints.get(EncodeHintType::CODE128_COMPACT).to_string());
|
||
return if has_compaction_hint { MinimalEncoder::new().encode(&contents) } else { ::encode_fast(&contents, forced_code_set) };
|
||
}
|
||
|
||
fn check( contents: &String, hints: &Map<EncodeHintType, ?>) -> i32 {
|
||
let length: i32 = contents.length();
|
||
// Check length
|
||
if length < 1 || length > 80 {
|
||
throw IllegalArgumentException::new(format!("Contents length should be between 1 and 80 characters, but got {}", length));
|
||
}
|
||
// Check for forced code set hint.
|
||
let forced_code_set: i32 = -1;
|
||
if hints != null && hints.contains_key(EncodeHintType::FORCE_CODE_SET) {
|
||
let code_set_hint: String = hints.get(EncodeHintType::FORCE_CODE_SET).to_string();
|
||
match code_set_hint {
|
||
"A" =>
|
||
{
|
||
forced_code_set = CODE_CODE_A;
|
||
break;
|
||
}
|
||
"B" =>
|
||
{
|
||
forced_code_set = CODE_CODE_B;
|
||
break;
|
||
}
|
||
"C" =>
|
||
{
|
||
forced_code_set = CODE_CODE_C;
|
||
break;
|
||
}
|
||
_ =>
|
||
{
|
||
throw IllegalArgumentException::new(format!("Unsupported code set hint: {}", code_set_hint));
|
||
}
|
||
}
|
||
}
|
||
// Check content
|
||
{
|
||
let mut i: i32 = 0;
|
||
while i < length {
|
||
{
|
||
let c: char = contents.char_at(i);
|
||
// check for non ascii characters that are not special GS1 characters
|
||
match c {
|
||
// special function characters
|
||
ESCAPE_FNC_1 =>
|
||
{
|
||
}
|
||
ESCAPE_FNC_2 =>
|
||
{
|
||
}
|
||
ESCAPE_FNC_3 =>
|
||
{
|
||
}
|
||
ESCAPE_FNC_4 =>
|
||
{
|
||
break;
|
||
}
|
||
// non ascii characters
|
||
_ =>
|
||
{
|
||
if c > 127 {
|
||
// shift and manual code change are not supported
|
||
throw IllegalArgumentException::new(format!("Bad character in input: ASCII value={}", c as i32));
|
||
}
|
||
}
|
||
}
|
||
// check characters for compatibility with forced code set
|
||
match forced_code_set {
|
||
CODE_CODE_A =>
|
||
{
|
||
// allows no ascii above 95 (no lower caps, no special symbols)
|
||
if c > 95 && c <= 127 {
|
||
throw IllegalArgumentException::new(format!("Bad character in input for forced code set A: ASCII value={}", c as i32));
|
||
}
|
||
break;
|
||
}
|
||
CODE_CODE_B =>
|
||
{
|
||
// allows no ascii below 32 (terminal symbols)
|
||
if c <= 32 {
|
||
throw IllegalArgumentException::new(format!("Bad character in input for forced code set B: ASCII value={}", c as i32));
|
||
}
|
||
break;
|
||
}
|
||
CODE_CODE_C =>
|
||
{
|
||
// allows only numbers and no FNC 2/3/4
|
||
if c < 48 || (c > 57 && c <= 127) || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 {
|
||
throw IllegalArgumentException::new(format!("Bad character in input for forced code set C: ASCII value={}", c as i32));
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
i += 1;
|
||
}
|
||
}
|
||
|
||
return forced_code_set;
|
||
}
|
||
|
||
fn encode_fast( contents: &String, forced_code_set: i32) -> Vec<bool> {
|
||
let length: i32 = contents.length();
|
||
// temporary storage for patterns
|
||
let patterns: Collection<Vec<i32>> = ArrayList<>::new();
|
||
let check_sum: i32 = 0;
|
||
let check_weight: i32 = 1;
|
||
// selected code (CODE_CODE_B or CODE_CODE_C)
|
||
let code_set: i32 = 0;
|
||
// position in contents
|
||
let mut position: i32 = 0;
|
||
while position < length {
|
||
//Select code to use
|
||
let new_code_set: i32;
|
||
if forced_code_set == -1 {
|
||
new_code_set = ::choose_code(&contents, position, code_set);
|
||
} else {
|
||
new_code_set = forced_code_set;
|
||
}
|
||
//Get the pattern index
|
||
let pattern_index: i32;
|
||
if new_code_set == code_set {
|
||
// First handle escapes
|
||
match contents.char_at(position) {
|
||
ESCAPE_FNC_1 =>
|
||
{
|
||
pattern_index = CODE_FNC_1;
|
||
break;
|
||
}
|
||
ESCAPE_FNC_2 =>
|
||
{
|
||
pattern_index = CODE_FNC_2;
|
||
break;
|
||
}
|
||
ESCAPE_FNC_3 =>
|
||
{
|
||
pattern_index = CODE_FNC_3;
|
||
break;
|
||
}
|
||
ESCAPE_FNC_4 =>
|
||
{
|
||
if code_set == CODE_CODE_A {
|
||
pattern_index = CODE_FNC_4_A;
|
||
} else {
|
||
pattern_index = CODE_FNC_4_B;
|
||
}
|
||
break;
|
||
}
|
||
_ =>
|
||
{
|
||
// Then handle normal characters otherwise
|
||
match code_set {
|
||
CODE_CODE_A =>
|
||
{
|
||
pattern_index = contents.char_at(position) - ' ';
|
||
if pattern_index < 0 {
|
||
// everything below a space character comes behind the underscore in the code patterns table
|
||
pattern_index += '`';
|
||
}
|
||
break;
|
||
}
|
||
CODE_CODE_B =>
|
||
{
|
||
pattern_index = contents.char_at(position) - ' ';
|
||
break;
|
||
}
|
||
_ =>
|
||
{
|
||
// CODE_CODE_C
|
||
if position + 1 == length {
|
||
// this is the last character, but the encoding is C, which always encodes two characers
|
||
throw IllegalArgumentException::new("Bad number of characters for digit only encoding.");
|
||
}
|
||
pattern_index = Integer::parse_int(&contents.substring(position, position + 2));
|
||
// Also incremented below
|
||
position += 1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
position += 1;
|
||
} else {
|
||
// Do we have a code set?
|
||
if code_set == 0 {
|
||
// No, we don't have a code set
|
||
match new_code_set {
|
||
CODE_CODE_A =>
|
||
{
|
||
pattern_index = CODE_START_A;
|
||
break;
|
||
}
|
||
CODE_CODE_B =>
|
||
{
|
||
pattern_index = CODE_START_B;
|
||
break;
|
||
}
|
||
_ =>
|
||
{
|
||
pattern_index = CODE_START_C;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// Yes, we have a code set
|
||
pattern_index = new_code_set;
|
||
}
|
||
code_set = new_code_set;
|
||
}
|
||
// Get the pattern
|
||
patterns.add(Code128Reader::CODE_PATTERNS[pattern_index]);
|
||
// Compute checksum
|
||
check_sum += pattern_index * check_weight;
|
||
if position != 0 {
|
||
check_weight += 1;
|
||
}
|
||
}
|
||
return ::produce_result(&patterns, check_sum);
|
||
}
|
||
|
||
fn produce_result( patterns: &Collection<Vec<i32>>, check_sum: i32) -> Vec<bool> {
|
||
// Compute and append checksum
|
||
check_sum %= 103;
|
||
patterns.add(Code128Reader::CODE_PATTERNS[check_sum]);
|
||
// Append stop code
|
||
patterns.add(Code128Reader::CODE_PATTERNS[CODE_STOP]);
|
||
// Compute code width
|
||
let code_width: i32 = 0;
|
||
for let pattern: Vec<i32> in patterns {
|
||
for let width: i32 in pattern {
|
||
code_width += width;
|
||
}
|
||
}
|
||
// Compute result
|
||
let result: [bool; code_width] = [false; code_width];
|
||
let mut pos: i32 = 0;
|
||
for let pattern: Vec<i32> in patterns {
|
||
pos += append_pattern(&result, pos, &pattern, true);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
fn find_c_type( value: &CharSequence, start: i32) -> CType {
|
||
let last: i32 = value.length();
|
||
if start >= last {
|
||
return CType.UNCODABLE;
|
||
}
|
||
let mut c: char = value.char_at(start);
|
||
if c == ESCAPE_FNC_1 {
|
||
return CType.FNC_1;
|
||
}
|
||
if c < '0' || c > '9' {
|
||
return CType.UNCODABLE;
|
||
}
|
||
if start + 1 >= last {
|
||
return CType.ONE_DIGIT;
|
||
}
|
||
c = value.char_at(start + 1);
|
||
if c < '0' || c > '9' {
|
||
return CType.ONE_DIGIT;
|
||
}
|
||
return CType.TWO_DIGITS;
|
||
}
|
||
|
||
fn choose_code( value: &CharSequence, start: i32, old_code: i32) -> i32 {
|
||
let mut lookahead: CType = ::find_c_type(&value, start);
|
||
if lookahead == CType.ONE_DIGIT {
|
||
if old_code == CODE_CODE_A {
|
||
return CODE_CODE_A;
|
||
}
|
||
return CODE_CODE_B;
|
||
}
|
||
if lookahead == CType.UNCODABLE {
|
||
if start < value.length() {
|
||
let c: char = value.char_at(start);
|
||
if c < ' ' || (old_code == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4))) {
|
||
// can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4
|
||
return CODE_CODE_A;
|
||
}
|
||
}
|
||
// no choice
|
||
return CODE_CODE_B;
|
||
}
|
||
if old_code == CODE_CODE_A && lookahead == CType.FNC_1 {
|
||
return CODE_CODE_A;
|
||
}
|
||
if old_code == CODE_CODE_C {
|
||
// can continue in code C
|
||
return CODE_CODE_C;
|
||
}
|
||
if old_code == CODE_CODE_B {
|
||
if lookahead == CType.FNC_1 {
|
||
// can continue in code B
|
||
return CODE_CODE_B;
|
||
}
|
||
// Seen two consecutive digits, see what follows
|
||
lookahead = ::find_c_type(&value, start + 2);
|
||
if lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT {
|
||
// not worth switching now
|
||
return CODE_CODE_B;
|
||
}
|
||
if lookahead == CType.FNC_1 {
|
||
// two digits, then FNC_1...
|
||
lookahead = ::find_c_type(&value, start + 3);
|
||
if lookahead == CType.TWO_DIGITS {
|
||
// then two more digits, switch
|
||
return CODE_CODE_C;
|
||
} else {
|
||
// otherwise not worth switching
|
||
return CODE_CODE_B;
|
||
}
|
||
}
|
||
// At this point, there are at least 4 consecutive digits.
|
||
// Look ahead to choose whether to switch now or on the next round.
|
||
let mut index: i32 = start + 4;
|
||
while (lookahead = ::find_c_type(&value, index)) == CType.TWO_DIGITS {
|
||
index += 2;
|
||
}
|
||
if lookahead == CType.ONE_DIGIT {
|
||
// odd number of digits, switch later
|
||
return CODE_CODE_B;
|
||
}
|
||
// even number of digits, switch now
|
||
return CODE_CODE_C;
|
||
}
|
||
// Here oldCode == 0, which means we are choosing the initial code
|
||
if lookahead == CType.FNC_1 {
|
||
// ignore FNC_1
|
||
lookahead = ::find_c_type(&value, start + 1);
|
||
}
|
||
if lookahead == CType.TWO_DIGITS {
|
||
// at least two digits, start in code C
|
||
return CODE_CODE_C;
|
||
}
|
||
return CODE_CODE_B;
|
||
}
|
||
|
||
/**
|
||
* Encodes minimally using Divide-And-Conquer with Memoization
|
||
**/
|
||
|
||
const A: &'static str = format!(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ |