Files
rxing/src/datamatrix/encoder/high_level_encoder.rs
2023-02-14 23:01:05 +00:00

619 lines
19 KiB
Rust

/*
* Copyright 2006-2007 Jeremias Maerki.
*
* 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::rc::Rc;
use encoding::{self, EncodingRef};
use crate::common::Result;
use crate::{Dimension, Exceptions};
use super::{
ASCIIEncoder, Base256Encoder, C40Encoder, EdifactEncoder, Encoder, EncoderContext,
SymbolInfoLookup, SymbolShapeHint, TextEncoder, X12Encoder,
};
#[allow(dead_code)]
const DEFAULT_ENCODING: EncodingRef = encoding::all::ISO_8859_1;
/**
* DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in
* annex S.
*/
/**
* Padding character
*/
const PAD: u8 = 129;
/**
* mode latch to C40 encodation mode
*/
pub const LATCH_TO_C40: u8 = 230;
/**
* mode latch to Base 256 encodation mode
*/
pub const LATCH_TO_BASE256: u8 = 231;
/**
* FNC1 Codeword
*/
//private static final char FNC1 = 232;
/**
* Structured Append Codeword
*/
//private static final char STRUCTURED_APPEND = 233;
/**
* Reader Programming
*/
//private static final char READER_PROGRAMMING = 234;
/**
* Upper Shift
*/
pub const UPPER_SHIFT: u8 = 235;
/**
* 05 Macro
*/
const MACRO_05: u8 = 236;
/**
* 06 Macro
*/
const MACRO_06: u8 = 237;
/**
* mode latch to ANSI X.12 encodation mode
*/
pub const LATCH_TO_ANSIX12: u8 = 238;
/**
* mode latch to Text encodation mode
*/
pub const LATCH_TO_TEXT: u8 = 239;
/**
* mode latch to EDIFACT encodation mode
*/
pub const LATCH_TO_EDIFACT: u8 = 240;
/**
* ECI character (Extended Channel Interpretation)
*/
//private static final char ECI = 241;
/**
* Unlatch from C40 encodation
*/
pub const C40_UNLATCH: u8 = 254;
/**
* Unlatch from X12 encodation
*/
pub const X12_UNLATCH: u8 = 254;
/**
* 05 Macro header
*/
pub const MACRO_05_HEADER: &str = "[)>\u{001E}05\u{001D}"; // THIS MIGHT BE WRONG, CHECK IF IT SHOULD BE a long unicode
/**
* 06 Macro header
*/
pub const MACRO_06_HEADER: &str = "[)>\u{001E}06\u{001D}"; // THIS MIGHT BE WRONG, CHECK IF IT SHOULD BE a long unicode
/**
* Macro trailer
*/
pub const MACRO_TRAILER: &str = "\u{001E}\u{0004}";
pub const ASCII_ENCODATION: usize = 0;
pub const C40_ENCODATION: usize = 1;
pub const TEXT_ENCODATION: usize = 2;
pub const X12_ENCODATION: usize = 3;
pub const EDIFACT_ENCODATION: usize = 4;
pub const BASE256_ENCODATION: usize = 5;
fn randomize253State(codewordPosition: u32) -> String {
let pseudoRandom = ((149 * codewordPosition) % 253) + 1;
let tempVariable = PAD as u32 + pseudoRandom;
if tempVariable <= 254 {
char::from_u32(tempVariable)
} else {
char::from_u32(tempVariable - 254)
}
// .expect("must become a char")
.unwrap_or(char::default())
.to_string()
// return (char) (tempVariable <= 254 ? tempVariable : tempVariable - 254);
}
/**
* Performs message encoding of a DataMatrix message using the algorithm described in annex P
* of ISO/IEC 16022:2000(E).
*
* @param msg the message
* @return the encoded message (the char values range from 0 to 255)
*/
pub fn encodeHighLevel(msg: &str) -> Result<String> {
encodeHighLevelWithDimensionForceC40(msg, SymbolShapeHint::FORCE_NONE, None, None, false)
}
/**
* Performs message encoding of a DataMatrix message using the algorithm described in annex P
* of ISO/IEC 16022:2000(E).
*
* @param msg the message
* @return the encoded message (the char values range from 0 to 255)
*/
pub fn encodeHighLevelSIL(
msg: &str,
symbol_lookup: Option<Rc<SymbolInfoLookup>>,
) -> Result<String> {
encodeHighLevelWithDimensionForceC40WithSymbolInfoLookup(
msg,
SymbolShapeHint::FORCE_NONE,
None,
None,
false,
symbol_lookup,
)
}
/**
* Performs message encoding of a DataMatrix message using the algorithm described in annex P
* of ISO/IEC 16022:2000(E).
*
* @param msg the message
* @param shape requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
* {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
* @param minSize the minimum symbol size constraint or null for no constraint
* @param maxSize the maximum symbol size constraint or null for no constraint
* @return the encoded message (the char values range from 0 to 255)
*/
pub fn encodeHighLevelWithDimension(
msg: &str,
shape: SymbolShapeHint,
minSize: Option<Dimension>,
maxSize: Option<Dimension>,
) -> Result<String> {
encodeHighLevelWithDimensionForceC40(msg, shape, minSize, maxSize, false)
}
pub fn encodeHighLevelWithDimensionForceC40WithSymbolInfoLookup(
msg: &str,
shape: SymbolShapeHint,
minSize: Option<Dimension>,
maxSize: Option<Dimension>,
forceC40: bool,
symbol_lookup: Option<Rc<SymbolInfoLookup>>,
) -> Result<String> {
//the codewords 0..255 are encoded as Unicode characters
let c40Encoder = Rc::new(C40Encoder::new());
let encoders: [Rc<dyn Encoder>; 6] = [
Rc::new(ASCIIEncoder::new()),
c40Encoder.clone(),
Rc::new(TextEncoder::new()),
Rc::new(X12Encoder::new()),
Rc::new(EdifactEncoder::new()),
Rc::new(Base256Encoder::new()),
];
let mut context = if let Some(symbol_table) = symbol_lookup {
EncoderContext::with_symbol_info_lookup(msg, symbol_table)?
} else {
EncoderContext::new(msg)?
};
// let mut context = EncoderContext::new(msg)?;
context.setSymbolShape(shape);
context.setSizeConstraints(minSize, maxSize);
if msg.starts_with(MACRO_05_HEADER) && msg.ends_with(MACRO_TRAILER) {
context.writeCodeword(MACRO_05);
context.setSkipAtEnd(2);
context.pos += MACRO_05_HEADER.chars().count() as u32;
} else if msg.starts_with(MACRO_06_HEADER) && msg.ends_with(MACRO_TRAILER) {
context.writeCodeword(MACRO_06);
context.setSkipAtEnd(2);
context.pos += MACRO_06_HEADER.chars().count() as u32;
}
let mut encodingMode = ASCII_ENCODATION; //Default mode
if forceC40 {
c40Encoder.encodeMaximalC40(&mut context)?;
encodingMode = context
.getNewEncoding()
.ok_or(Exceptions::IllegalStateException(None))?;
context.resetEncoderSignal();
}
while context.hasMoreCharacters() {
encoders[encodingMode].encode(&mut context)?;
if context.getNewEncoding().is_some() {
encodingMode = context
.getNewEncoding()
.ok_or(Exceptions::IllegalStateException(None))?;
context.resetEncoderSignal();
}
}
let len = context.getCodewordCount();
context.updateSymbolInfo();
let capacity = context
.getSymbolInfo()
.ok_or(Exceptions::IllegalStateException(None))?
.getDataCapacity();
if len < capacity as usize
&& encodingMode != ASCII_ENCODATION
&& encodingMode != BASE256_ENCODATION
&& encodingMode != EDIFACT_ENCODATION
{
context.writeCodeword(0xfe); //Unlatch (254)
// context.writeCodeword("\u{00fe}"); //Unlatch (254)
}
//Padding
// let codewords = context.getCodewords();
if context.getCodewords().chars().count() < capacity as usize {
// codewords.push(PAD as char);
context.writeCodeword(PAD)
}
while context.getCodewords().chars().count() < capacity as usize {
// codewords.append(randomize253State(codewords.len() + 1));
context.writeCodewords(&randomize253State(
context.getCodewords().chars().count() as u32 + 1,
))
}
Ok(context.getCodewords().to_owned())
}
/**
* Performs message encoding of a DataMatrix message using the algorithm described in annex P
* of ISO/IEC 16022:2000(E).
*
* @param msg the message
* @param shape requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
* {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
* @param minSize the minimum symbol size constraint or null for no constraint
* @param maxSize the maximum symbol size constraint or null for no constraint
* @param forceC40 enforce C40 encoding
* @return the encoded message (the char values range from 0 to 255)
*/
pub fn encodeHighLevelWithDimensionForceC40(
msg: &str,
shape: SymbolShapeHint,
minSize: Option<Dimension>,
maxSize: Option<Dimension>,
forceC40: bool,
) -> Result<String> {
encodeHighLevelWithDimensionForceC40WithSymbolInfoLookup(
msg, shape, minSize, maxSize, forceC40, None,
)
}
pub fn lookAheadTest(msg: &str, startpos: u32, currentMode: u32) -> usize {
let newMode = lookAheadTestIntern(msg, startpos, currentMode);
if currentMode as usize == X12_ENCODATION && newMode == X12_ENCODATION {
// let msg_graphemes = msg.graphemes(true);
let endpos = (startpos + 3).min(msg.chars().count() as u32);
for i in startpos..endpos {
// for (int i = startpos; i < endpos; i++) {
if let Some(c) = msg.chars().nth(i as usize) {
if !isNativeX12(c) {
return ASCII_ENCODATION;
}
}
}
} else if currentMode as usize == EDIFACT_ENCODATION && newMode == EDIFACT_ENCODATION {
// let msg_graphemes = msg.graphemes(true);
let endpos = (startpos + 4).min(msg.chars().count() as u32);
for i in startpos..endpos {
// for (int i = startpos; i < endpos; i++) {
if let Some(c) = msg.chars().nth(i as usize) {
if !isNativeEDIFACT(c) {
return ASCII_ENCODATION;
}
}
}
}
newMode
}
fn lookAheadTestIntern(msg: &str, startpos: u32, currentMode: u32) -> usize {
if startpos as usize >= msg.chars().count() {
return currentMode as usize;
}
let mut charCounts: [f32; 6];
//step J
if currentMode == ASCII_ENCODATION as u32 {
charCounts = [0.0, 1.0, 1.0, 1.0, 1.0, 1.25];
} else {
charCounts = [1.0, 2.0, 2.0, 2.0, 2.0, 2.25];
charCounts[currentMode as usize] = 0.0;
}
let mut charsProcessed = 0;
let mut mins = [0u8; 6];
let mut intCharCounts = [0u32; 6];
loop {
//step K
if (startpos + charsProcessed) == msg.chars().count() as u32 {
mins.fill(0);
intCharCounts.fill(0);
// Arrays.fill(mins, (byte) 0);
// Arrays.fill(intCharCounts, 0);
let min = findMinimums(&charCounts, &mut intCharCounts, u32::MAX, &mut mins);
let minCount = getMinimumCount(&mins);
if intCharCounts[ASCII_ENCODATION] == min {
return ASCII_ENCODATION;
}
if minCount == 1 {
if mins[BASE256_ENCODATION] > 0 {
return BASE256_ENCODATION;
}
if mins[EDIFACT_ENCODATION] > 0 {
return EDIFACT_ENCODATION;
}
if mins[TEXT_ENCODATION] > 0 {
return TEXT_ENCODATION;
}
if mins[X12_ENCODATION] > 0 {
return X12_ENCODATION;
}
}
return C40_ENCODATION;
}
let Some(c) = msg
.chars()
.nth((startpos + charsProcessed) as usize) else {
break 0;
};
charsProcessed += 1;
//step L
if isDigit(c) {
charCounts[ASCII_ENCODATION] += 0.5;
} else if isExtendedASCII(c) {
charCounts[ASCII_ENCODATION] = charCounts[ASCII_ENCODATION].ceil();
charCounts[ASCII_ENCODATION] += 2.0;
} else {
charCounts[ASCII_ENCODATION] = charCounts[ASCII_ENCODATION].ceil();
charCounts[ASCII_ENCODATION] += 1.0;
}
//step M
if isNativeC40(c) {
charCounts[C40_ENCODATION] += 2.0 / 3.0;
} else if isExtendedASCII(c) {
charCounts[C40_ENCODATION] += 8.0 / 3.0;
} else {
charCounts[C40_ENCODATION] += 4.0 / 3.0;
}
//step N
if isNativeText(c) {
charCounts[TEXT_ENCODATION] += 2.0 / 3.0;
} else if isExtendedASCII(c) {
charCounts[TEXT_ENCODATION] += 8.0 / 3.0;
} else {
charCounts[TEXT_ENCODATION] += 4.0 / 3.0;
}
//step O
if isNativeX12(c) {
charCounts[X12_ENCODATION] += 2.0 / 3.0;
} else if isExtendedASCII(c) {
charCounts[X12_ENCODATION] += 13.0 / 3.0;
} else {
charCounts[X12_ENCODATION] += 10.0 / 3.0;
}
//step P
if isNativeEDIFACT(c) {
charCounts[EDIFACT_ENCODATION] += 3.0 / 4.0;
} else if isExtendedASCII(c) {
charCounts[EDIFACT_ENCODATION] += 17.0 / 4.0;
} else {
charCounts[EDIFACT_ENCODATION] += 13.0 / 4.0;
}
// step Q
if isSpecialB256(c) {
charCounts[BASE256_ENCODATION] += 4.0;
} else {
charCounts[BASE256_ENCODATION] += 1.0;
}
//step R
if charsProcessed >= 4 {
mins.fill(0);
intCharCounts.fill(0);
// Arrays.fill(mins, (byte) 0);
// Arrays.fill(intCharCounts, 0);
findMinimums(&charCounts, &mut intCharCounts, u32::MAX, &mut mins);
if intCharCounts[ASCII_ENCODATION]
< min5(
intCharCounts[BASE256_ENCODATION],
intCharCounts[C40_ENCODATION],
intCharCounts[TEXT_ENCODATION],
intCharCounts[X12_ENCODATION],
intCharCounts[EDIFACT_ENCODATION],
)
{
return ASCII_ENCODATION;
}
if intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION]
|| intCharCounts[BASE256_ENCODATION] + 1
< min4(
intCharCounts[C40_ENCODATION],
intCharCounts[TEXT_ENCODATION],
intCharCounts[X12_ENCODATION],
intCharCounts[EDIFACT_ENCODATION],
)
{
return BASE256_ENCODATION;
}
if intCharCounts[EDIFACT_ENCODATION] + 1
< min5(
intCharCounts[BASE256_ENCODATION],
intCharCounts[C40_ENCODATION],
intCharCounts[TEXT_ENCODATION],
intCharCounts[X12_ENCODATION],
intCharCounts[ASCII_ENCODATION],
)
{
return EDIFACT_ENCODATION;
}
if intCharCounts[TEXT_ENCODATION] + 1
< min5(
intCharCounts[BASE256_ENCODATION],
intCharCounts[C40_ENCODATION],
intCharCounts[EDIFACT_ENCODATION],
intCharCounts[X12_ENCODATION],
intCharCounts[ASCII_ENCODATION],
)
{
return TEXT_ENCODATION;
}
if intCharCounts[X12_ENCODATION] + 1
< min5(
intCharCounts[BASE256_ENCODATION],
intCharCounts[C40_ENCODATION],
intCharCounts[EDIFACT_ENCODATION],
intCharCounts[TEXT_ENCODATION],
intCharCounts[ASCII_ENCODATION],
)
{
return X12_ENCODATION;
}
if intCharCounts[C40_ENCODATION] + 1
< min4(
intCharCounts[ASCII_ENCODATION],
intCharCounts[BASE256_ENCODATION],
intCharCounts[EDIFACT_ENCODATION],
intCharCounts[TEXT_ENCODATION],
)
{
if intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION] {
return C40_ENCODATION;
}
if intCharCounts[C40_ENCODATION] == intCharCounts[X12_ENCODATION] {
let mut _p = startpos + charsProcessed + 1;
for tc in msg.chars() {
// while (p as usize) < msg.len() {
// let tc = msg.charAt(p);
if isX12TermSep(tc) {
return X12_ENCODATION;
}
if !isNativeX12(tc) {
break;
}
_p += 1;
}
return C40_ENCODATION;
}
}
}
}
}
fn min5(f1: u32, f2: u32, f3: u32, f4: u32, f5: u32) -> u32 {
min4(f1, f2, f3, f4).min(f5)
}
fn min4(f1: u32, f2: u32, f3: u32, f4: u32) -> u32 {
f1.min(f2.min(f3.min(f4)))
// Math.min(f1, Math.min(f2, Math.min(f3, f4)))
}
fn findMinimums(
charCounts: &[f32; 6],
intCharCounts: &mut [u32; 6],
min: u32,
mins: &mut [u8],
) -> u32 {
let mut min = min;
for i in 0..6 {
// for (int i = 0; i < 6; i++) {
intCharCounts[i] = charCounts[i].ceil() as u32;
let current = intCharCounts[i]; // = (int) Math.ceil(charCounts[i]));
if min > current {
min = current;
mins.fill(0);
// Arrays.fill(mins, (byte) 0);
}
if min == current {
mins[i] += 1;
}
}
min
}
fn getMinimumCount(mins: &[u8]) -> u32 {
mins.iter().take(6).sum::<u8>() as u32
}
pub fn isDigit(ch: char) -> bool {
('0'..='9').contains(&ch)
}
pub fn isExtendedASCII(ch: char) -> bool {
(ch as u8) >= 128 //&& (ch as u8) <= 255
}
pub fn isNativeC40(ch: char) -> bool {
(ch == ' ') || ('0'..='9').contains(&ch) || ('A'..='Z').contains(&ch)
}
pub fn isNativeText(ch: char) -> bool {
(ch == ' ') || ('0'..='9').contains(&ch) || ('a'..='z').contains(&ch)
}
pub fn isNativeX12(ch: char) -> bool {
isX12TermSep(ch) || (ch == ' ') || ('0'..='9').contains(&ch) || ('A'..='Z').contains(&ch)
}
fn isX12TermSep(ch: char) -> bool {
(ch == '\r') //CR
|| (ch == '*')
|| (ch == '>')
}
pub fn isNativeEDIFACT(ch: char) -> bool {
(' '..='^').contains(&ch)
}
fn isSpecialB256(_ch: char) -> bool {
// unimplemented!();
false //TODO NOT IMPLEMENTED YET!!!
}
/**
* Determines the number of consecutive characters that are encodable using numeric compaction.
*
* @param msg the message
* @param startpos the start position within the message
* @return the requested character count
*/
pub fn determineConsecutiveDigitCount(msg: &str, startpos: u32) -> u32 {
let len = msg.chars().count(); //len();
let mut idx = startpos;
// let graphemes = msg.graphemes(true);
while (idx as usize) < len && isDigit(msg.chars().nth(idx as usize).unwrap_or(char::default()))
{
idx += 1;
}
idx - startpos
}
pub fn illegalCharacter(c: char) -> Result<()> {
// let hex = Integer.toHexString(c);
// hex = "0000".substring(0, 4 - hex.length()) + hex;
Err(Exceptions::IllegalArgumentException(Some(format!(
"Illegal character: {c} (0x{c})"
))))
}