mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
port code_128 reader
This commit is contained in:
@@ -325,5 +325,5 @@ pub enum EncodeHintValue {
|
||||
* This can yield slightly smaller bar codes. This option and {@link #FORCE_CODE_SET} are mutually
|
||||
* exclusive.
|
||||
*/
|
||||
Code128Compact(String),
|
||||
Code128Compact(bool),
|
||||
}
|
||||
|
||||
@@ -1,567 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This object renders a CODE128 code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||||
*/
|
||||
public final class Code128Writer extends OneDimensionalCodeWriter {
|
||||
|
||||
private static final int CODE_START_A = 103;
|
||||
private static final int CODE_START_B = 104;
|
||||
private static final int CODE_START_C = 105;
|
||||
private static final int CODE_CODE_A = 101;
|
||||
private static final int CODE_CODE_B = 100;
|
||||
private static final int CODE_CODE_C = 99;
|
||||
private static final int CODE_STOP = 106;
|
||||
|
||||
// Dummy characters used to specify control characters in input
|
||||
private static final char ESCAPE_FNC_1 = '\u00f1';
|
||||
private static final char ESCAPE_FNC_2 = '\u00f2';
|
||||
private static final char ESCAPE_FNC_3 = '\u00f3';
|
||||
private static final char ESCAPE_FNC_4 = '\u00f4';
|
||||
|
||||
private static final int CODE_FNC_1 = 102; // Code A, Code B, Code C
|
||||
private static final int CODE_FNC_2 = 97; // Code A, Code B
|
||||
private static final int CODE_FNC_3 = 96; // Code A, Code B
|
||||
private static final int CODE_FNC_4_A = 101; // Code A
|
||||
private static final int CODE_FNC_4_B = 100; // Code B
|
||||
|
||||
// RXingResults of minimal lookahead for code C
|
||||
private enum CType {
|
||||
UNCODABLE,
|
||||
ONE_DIGIT,
|
||||
TWO_DIGITS,
|
||||
FNC_1
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.CODE_128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
return encode(contents, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean[] encode(String contents, Map<EncodeHintType,?> hints) {
|
||||
|
||||
int forcedCodeSet = check(contents, hints);
|
||||
|
||||
boolean hasCompactionHint = hints != null && hints.containsKey(EncodeHintType.CODE128_COMPACT) &&
|
||||
Boolean.parseBoolean(hints.get(EncodeHintType.CODE128_COMPACT).toString());
|
||||
|
||||
return hasCompactionHint ? new MinimalEncoder().encode(contents) : encodeFast(contents, forcedCodeSet);
|
||||
}
|
||||
|
||||
private static int check(String contents, Map<EncodeHintType,?> hints) {
|
||||
int length = contents.length();
|
||||
// Check length
|
||||
if (length < 1 || length > 80) {
|
||||
throw new IllegalArgumentException(
|
||||
"Contents length should be between 1 and 80 characters, but got " + length);
|
||||
}
|
||||
|
||||
// Check for forced code set hint.
|
||||
int forcedCodeSet = -1;
|
||||
if (hints != null && hints.containsKey(EncodeHintType.FORCE_CODE_SET)) {
|
||||
String codeSetHint = hints.get(EncodeHintType.FORCE_CODE_SET).toString();
|
||||
switch (codeSetHint) {
|
||||
case "A":
|
||||
forcedCodeSet = CODE_CODE_A;
|
||||
break;
|
||||
case "B":
|
||||
forcedCodeSet = CODE_CODE_B;
|
||||
break;
|
||||
case "C":
|
||||
forcedCodeSet = CODE_CODE_C;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported code set hint: " + codeSetHint);
|
||||
}
|
||||
}
|
||||
|
||||
// Check content
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = contents.charAt(i);
|
||||
// check for non ascii characters that are not special GS1 characters
|
||||
switch (c) {
|
||||
// special function characters
|
||||
case ESCAPE_FNC_1:
|
||||
case ESCAPE_FNC_2:
|
||||
case ESCAPE_FNC_3:
|
||||
case ESCAPE_FNC_4:
|
||||
break;
|
||||
// non ascii characters
|
||||
default:
|
||||
if (c > 127) {
|
||||
// no full Latin-1 character set available at the moment
|
||||
// shift and manual code change are not supported
|
||||
throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) c);
|
||||
}
|
||||
}
|
||||
// check characters for compatibility with forced code set
|
||||
switch (forcedCodeSet) {
|
||||
case CODE_CODE_A:
|
||||
// allows no ascii above 95 (no lower caps, no special symbols)
|
||||
if (c > 95 && c <= 127) {
|
||||
throw new IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + (int) c);
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
// allows no ascii below 32 (terminal symbols)
|
||||
if (c <= 32) {
|
||||
throw new IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + (int) c);
|
||||
}
|
||||
break;
|
||||
case 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 new IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + (int) c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return forcedCodeSet;
|
||||
}
|
||||
|
||||
private static boolean[] encodeFast(String contents, int forcedCodeSet) {
|
||||
int length = contents.length();
|
||||
|
||||
Collection<int[]> patterns = new ArrayList<>(); // temporary storage for patterns
|
||||
int checkSum = 0;
|
||||
int checkWeight = 1;
|
||||
int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
|
||||
int position = 0; // position in contents
|
||||
|
||||
while (position < length) {
|
||||
//Select code to use
|
||||
int newCodeSet;
|
||||
if (forcedCodeSet == -1) {
|
||||
newCodeSet = chooseCode(contents, position, codeSet);
|
||||
} else {
|
||||
newCodeSet = forcedCodeSet;
|
||||
}
|
||||
|
||||
//Get the pattern index
|
||||
int patternIndex;
|
||||
if (newCodeSet == codeSet) {
|
||||
// Encode the current character
|
||||
// First handle escapes
|
||||
switch (contents.charAt(position)) {
|
||||
case ESCAPE_FNC_1:
|
||||
patternIndex = CODE_FNC_1;
|
||||
break;
|
||||
case ESCAPE_FNC_2:
|
||||
patternIndex = CODE_FNC_2;
|
||||
break;
|
||||
case ESCAPE_FNC_3:
|
||||
patternIndex = CODE_FNC_3;
|
||||
break;
|
||||
case ESCAPE_FNC_4:
|
||||
if (codeSet == CODE_CODE_A) {
|
||||
patternIndex = CODE_FNC_4_A;
|
||||
} else {
|
||||
patternIndex = CODE_FNC_4_B;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Then handle normal characters otherwise
|
||||
switch (codeSet) {
|
||||
case CODE_CODE_A:
|
||||
patternIndex = contents.charAt(position) - ' ';
|
||||
if (patternIndex < 0) {
|
||||
// everything below a space character comes behind the underscore in the code patterns table
|
||||
patternIndex += '`';
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
patternIndex = contents.charAt(position) - ' ';
|
||||
break;
|
||||
default:
|
||||
// CODE_CODE_C
|
||||
if (position + 1 == length) {
|
||||
// this is the last character, but the encoding is C, which always encodes two characers
|
||||
throw new IllegalArgumentException("Bad number of characters for digit only encoding.");
|
||||
}
|
||||
patternIndex = Integer.parseInt(contents.substring(position, position + 2));
|
||||
position++; // Also incremented below
|
||||
break;
|
||||
}
|
||||
}
|
||||
position++;
|
||||
} else {
|
||||
// Should we change the current code?
|
||||
// Do we have a code set?
|
||||
if (codeSet == 0) {
|
||||
// No, we don't have a code set
|
||||
switch (newCodeSet) {
|
||||
case CODE_CODE_A:
|
||||
patternIndex = CODE_START_A;
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
patternIndex = CODE_START_B;
|
||||
break;
|
||||
default:
|
||||
patternIndex = CODE_START_C;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Yes, we have a code set
|
||||
patternIndex = newCodeSet;
|
||||
}
|
||||
codeSet = newCodeSet;
|
||||
}
|
||||
|
||||
// Get the pattern
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
|
||||
|
||||
// Compute checksum
|
||||
checkSum += patternIndex * checkWeight;
|
||||
if (position != 0) {
|
||||
checkWeight++;
|
||||
}
|
||||
}
|
||||
return produceRXingResult(patterns, checkSum);
|
||||
}
|
||||
|
||||
static boolean[] produceRXingResult(Collection<int[]> patterns, int checkSum) {
|
||||
// Compute and append checksum
|
||||
checkSum %= 103;
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[checkSum]);
|
||||
|
||||
// Append stop code
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]);
|
||||
|
||||
// Compute code width
|
||||
int codeWidth = 0;
|
||||
for (int[] pattern : patterns) {
|
||||
for (int width : pattern) {
|
||||
codeWidth += width;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute result
|
||||
boolean[] result = new boolean[codeWidth];
|
||||
int pos = 0;
|
||||
for (int[] pattern : patterns) {
|
||||
pos += appendPattern(result, pos, pattern, true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static CType findCType(CharSequence value, int start) {
|
||||
int last = value.length();
|
||||
if (start >= last) {
|
||||
return CType.UNCODABLE;
|
||||
}
|
||||
char c = value.charAt(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.charAt(start + 1);
|
||||
if (c < '0' || c > '9') {
|
||||
return CType.ONE_DIGIT;
|
||||
}
|
||||
return CType.TWO_DIGITS;
|
||||
}
|
||||
|
||||
private static int chooseCode(CharSequence value, int start, int oldCode) {
|
||||
CType lookahead = findCType(value, start);
|
||||
if (lookahead == CType.ONE_DIGIT) {
|
||||
if (oldCode == CODE_CODE_A) {
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
if (lookahead == CType.UNCODABLE) {
|
||||
if (start < value.length()) {
|
||||
char c = value.charAt(start);
|
||||
if (c < ' ' || (oldCode == 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;
|
||||
}
|
||||
}
|
||||
return CODE_CODE_B; // no choice
|
||||
}
|
||||
if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) {
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
if (oldCode == CODE_CODE_C) { // can continue in code C
|
||||
return CODE_CODE_C;
|
||||
}
|
||||
if (oldCode == CODE_CODE_B) {
|
||||
if (lookahead == CType.FNC_1) {
|
||||
return CODE_CODE_B; // can continue in code B
|
||||
}
|
||||
// Seen two consecutive digits, see what follows
|
||||
lookahead = findCType(value, start + 2);
|
||||
if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) {
|
||||
return CODE_CODE_B; // not worth switching now
|
||||
}
|
||||
if (lookahead == CType.FNC_1) { // two digits, then FNC_1...
|
||||
lookahead = findCType(value, start + 3);
|
||||
if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch
|
||||
return CODE_CODE_C;
|
||||
} else {
|
||||
return CODE_CODE_B; // otherwise not worth switching
|
||||
}
|
||||
}
|
||||
// At this point, there are at least 4 consecutive digits.
|
||||
// Look ahead to choose whether to switch now or on the next round.
|
||||
int index = start + 4;
|
||||
while ((lookahead = findCType(value, index)) == CType.TWO_DIGITS) {
|
||||
index += 2;
|
||||
}
|
||||
if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
return CODE_CODE_C; // even number of digits, switch now
|
||||
}
|
||||
// Here oldCode == 0, which means we are choosing the initial code
|
||||
if (lookahead == CType.FNC_1) { // ignore FNC_1
|
||||
lookahead = findCType(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
|
||||
**/
|
||||
private static final class MinimalEncoder {
|
||||
|
||||
private enum Charset { A, B, C, NONE }
|
||||
private enum Latch { A, B, C, SHIFT, NONE }
|
||||
|
||||
static final String A = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" +
|
||||
"\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" +
|
||||
"\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" +
|
||||
"\u00FF";
|
||||
static final String B = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" +
|
||||
"stuvwxyz{|}~\u007F\u00FF";
|
||||
|
||||
private static final int CODE_SHIFT = 98;
|
||||
|
||||
private int[][] memoizedCost;
|
||||
private Latch[][] minPath;
|
||||
|
||||
private boolean[] encode(String contents) {
|
||||
memoizedCost = new int[4][contents.length()];
|
||||
minPath = new Latch[4][contents.length()];
|
||||
|
||||
encode(contents, Charset.NONE, 0);
|
||||
|
||||
Collection<int[]> patterns = new ArrayList<>();
|
||||
int[] checkSum = new int[] {0};
|
||||
int[] checkWeight = new int[] {1};
|
||||
int length = contents.length();
|
||||
Charset charset = Charset.NONE;
|
||||
for (int i = 0; i < length; i++) {
|
||||
Latch latch = minPath[charset.ordinal()][i];
|
||||
switch (latch) {
|
||||
case A:
|
||||
charset = Charset.A;
|
||||
addPattern(patterns, i == 0 ? CODE_START_A : CODE_CODE_A, checkSum, checkWeight, i);
|
||||
break;
|
||||
case B:
|
||||
charset = Charset.B;
|
||||
addPattern(patterns, i == 0 ? CODE_START_B : CODE_CODE_B, checkSum, checkWeight, i);
|
||||
break;
|
||||
case C:
|
||||
charset = Charset.C;
|
||||
addPattern(patterns, i == 0 ? CODE_START_C : CODE_CODE_C, checkSum, checkWeight, i);
|
||||
break;
|
||||
case SHIFT:
|
||||
addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i);
|
||||
break;
|
||||
}
|
||||
if (charset == Charset.C) {
|
||||
if (contents.charAt(i) == ESCAPE_FNC_1) {
|
||||
addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i);
|
||||
} else {
|
||||
addPattern(patterns, Integer.parseInt(contents.substring(i, i + 2)), checkSum, checkWeight, i);
|
||||
assert i + 1 < length; //the algorithm never leads to a single trailing digit in character set C
|
||||
if (i + 1 < length) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else { // charset A or B
|
||||
int patternIndex;
|
||||
switch (contents.charAt(i)) {
|
||||
case ESCAPE_FNC_1:
|
||||
patternIndex = CODE_FNC_1;
|
||||
break;
|
||||
case ESCAPE_FNC_2:
|
||||
patternIndex = CODE_FNC_2;
|
||||
break;
|
||||
case ESCAPE_FNC_3:
|
||||
patternIndex = CODE_FNC_3;
|
||||
break;
|
||||
case ESCAPE_FNC_4:
|
||||
if ((charset == Charset.A && latch != Latch.SHIFT) ||
|
||||
(charset == Charset.B && latch == Latch.SHIFT)) {
|
||||
patternIndex = CODE_FNC_4_A;
|
||||
} else {
|
||||
patternIndex = CODE_FNC_4_B;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
patternIndex = contents.charAt(i) - ' ';
|
||||
}
|
||||
if ((charset == Charset.A && latch != Latch.SHIFT) ||
|
||||
(charset == Charset.B && latch == Latch.SHIFT)) {
|
||||
if (patternIndex < 0) {
|
||||
patternIndex += '`';
|
||||
}
|
||||
}
|
||||
addPattern(patterns, patternIndex, checkSum, checkWeight, i);
|
||||
}
|
||||
}
|
||||
memoizedCost = null;
|
||||
minPath = null;
|
||||
return produceRXingResult(patterns, checkSum[0]);
|
||||
}
|
||||
|
||||
private static void addPattern(Collection<int[]> patterns,
|
||||
int patternIndex,
|
||||
int[] checkSum,
|
||||
int[] checkWeight,
|
||||
int position) {
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
|
||||
if (position != 0) {
|
||||
checkWeight[0]++;
|
||||
}
|
||||
checkSum[0] += patternIndex * checkWeight[0];
|
||||
}
|
||||
|
||||
private static boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean canEncode(CharSequence contents, Charset charset,int position) {
|
||||
char c = contents.charAt(position);
|
||||
switch (charset) {
|
||||
case A: return c == ESCAPE_FNC_1 ||
|
||||
c == ESCAPE_FNC_2 ||
|
||||
c == ESCAPE_FNC_3 ||
|
||||
c == ESCAPE_FNC_4 ||
|
||||
A.indexOf(c) >= 0;
|
||||
case B: return c == ESCAPE_FNC_1 ||
|
||||
c == ESCAPE_FNC_2 ||
|
||||
c == ESCAPE_FNC_3 ||
|
||||
c == ESCAPE_FNC_4 ||
|
||||
B.indexOf(c) >= 0;
|
||||
case C: return c == ESCAPE_FNC_1 ||
|
||||
(position + 1 < contents.length() &&
|
||||
isDigit(c) &&
|
||||
isDigit(contents.charAt(position + 1)));
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the string starting at position position starting with the character set charset
|
||||
**/
|
||||
private int encode(CharSequence contents, Charset charset, int position) {
|
||||
assert position < contents.length();
|
||||
int mCost = memoizedCost[charset.ordinal()][position];
|
||||
if (mCost > 0) {
|
||||
return mCost;
|
||||
}
|
||||
|
||||
int minCost = Integer.MAX_VALUE;
|
||||
Latch minLatch = Latch.NONE;
|
||||
boolean atEnd = position + 1 >= contents.length();
|
||||
|
||||
Charset[] sets = new Charset[] { Charset.A, Charset.B };
|
||||
for (int i = 0; i <= 1; i++) {
|
||||
if (canEncode(contents, sets[i], position)) {
|
||||
int cost = 1;
|
||||
Latch latch = Latch.NONE;
|
||||
if (charset != sets[i]) {
|
||||
cost++;
|
||||
latch = Latch.valueOf(sets[i].toString());
|
||||
}
|
||||
if (!atEnd) {
|
||||
cost += encode(contents, sets[i], position + 1);
|
||||
}
|
||||
if (cost < minCost) {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
cost = 1;
|
||||
if (charset == sets[(i + 1) % 2]) {
|
||||
cost++;
|
||||
latch = Latch.SHIFT;
|
||||
if (!atEnd) {
|
||||
cost += encode(contents, charset, position + 1);
|
||||
}
|
||||
if (cost < minCost) {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canEncode(contents, Charset.C, position)) {
|
||||
int cost = 1;
|
||||
Latch latch = Latch.NONE;
|
||||
if (charset != Charset.C) {
|
||||
cost++;
|
||||
latch = Latch.C;
|
||||
}
|
||||
int advance = contents.charAt(position) == ESCAPE_FNC_1 ? 1 : 2;
|
||||
if (position + advance < contents.length()) {
|
||||
cost += encode(contents, Charset.C, position + advance);
|
||||
}
|
||||
if (cost < minCost) {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
}
|
||||
if (minCost == Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position));
|
||||
}
|
||||
memoizedCost[charset.ordinal()][position] = minCost;
|
||||
minPath[charset.ordinal()][position] = minLatch;
|
||||
return minCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,7 +453,7 @@ use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
|
||||
static ref CODE_PATTERNS: [Vec<u32>; 107] = [
|
||||
pub static ref CODE_PATTERNS: [Vec<u32>; 107] = [
|
||||
vec![2, 1, 2, 2, 2, 2], // 0
|
||||
vec![2, 2, 2, 1, 2, 2],
|
||||
vec![2, 2, 2, 2, 2, 1],
|
||||
|
||||
775
src/oned/code_128_writer.rs
Normal file
775
src/oned/code_128_writer.rs
Normal file
@@ -0,0 +1,775 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use one_d_reader_derive::OneDWriter;
|
||||
|
||||
use crate::BarcodeFormat;
|
||||
|
||||
use super::{code_128_reader, Code128Reader, OneDimensionalCodeWriter, CODE_PATTERNS};
|
||||
|
||||
const CODE_START_A: usize = 103;
|
||||
const CODE_START_B: usize = 104;
|
||||
const CODE_START_C: usize = 105;
|
||||
const CODE_CODE_A: usize = 101;
|
||||
const CODE_CODE_B: usize = 100;
|
||||
const CODE_CODE_C: usize = 99;
|
||||
const CODE_STOP: usize = 106;
|
||||
|
||||
// Dummy characters used to specify control characters in input
|
||||
const ESCAPE_FNC_1: char = '\u{00f1}';
|
||||
const ESCAPE_FNC_2: char = '\u{00f2}';
|
||||
const ESCAPE_FNC_3: char = '\u{00f3}';
|
||||
const ESCAPE_FNC_4: char = '\u{00f4}';
|
||||
|
||||
const CODE_FNC_1: usize = 102; // Code A, Code B, Code C
|
||||
const CODE_FNC_2: usize = 97; // Code A, Code B
|
||||
const CODE_FNC_3: usize = 96; // Code A, Code B
|
||||
const CODE_FNC_4_A: usize = 101; // Code A
|
||||
const CODE_FNC_4_B: usize = 100; // Code B
|
||||
|
||||
// RXingResults of minimal lookahead for code C
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum CType {
|
||||
UNCODABLE,
|
||||
ONE_DIGIT,
|
||||
TWO_DIGITS,
|
||||
FNC_1,
|
||||
}
|
||||
|
||||
/**
|
||||
* This object renders a CODE128 code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||||
*/
|
||||
#[derive(OneDWriter)]
|
||||
pub struct Code128Writer;
|
||||
impl Default for Code128Writer {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
impl OneDimensionalCodeWriter for Code128Writer {
|
||||
fn encode_oned(&self, contents: &str) -> Result<Vec<bool>, Exceptions> {
|
||||
self.encode_oned_with_hints(contents, &HashMap::new())
|
||||
}
|
||||
|
||||
fn getSupportedWriteFormats(&self) -> Option<Vec<crate::BarcodeFormat>> {
|
||||
Some(vec![BarcodeFormat::CODE_128])
|
||||
}
|
||||
|
||||
fn encode_oned_with_hints(
|
||||
&self,
|
||||
contents: &str,
|
||||
hints: &crate::EncodingHintDictionary,
|
||||
) -> Result<Vec<bool>, Exceptions> {
|
||||
let forcedCodeSet = check(contents, hints)?;
|
||||
|
||||
let hasCompactionHint = if let Some(EncodeHintValue::Code128Compact(compat)) =
|
||||
hints.get(&EncodeHintType::CODE128_COMPACT)
|
||||
{
|
||||
*compat
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// let hasCompactionHint = hints != null && hints.containsKey(EncodeHintType::CODE128_COMPACT) &&
|
||||
// Boolean.parseBoolean(hints.get(EncodeHintType::CODE128_COMPACT).toString());
|
||||
|
||||
if hasCompactionHint {
|
||||
MinimalEncoder::encode(contents)
|
||||
} else {
|
||||
encodeFast(contents, forcedCodeSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check(contents: &str, hints: &crate::EncodingHintDictionary) -> Result<i32, Exceptions> {
|
||||
let length = contents.chars().count();
|
||||
// Check length
|
||||
if length < 1 || length > 80 {
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Contents length should be between 1 and 80 characters, but got {}",
|
||||
length
|
||||
)));
|
||||
}
|
||||
|
||||
// Check for forced code set hint.
|
||||
let mut forcedCodeSet = -1_i32;
|
||||
if hints.contains_key(&EncodeHintType::FORCE_CODE_SET) {
|
||||
let Some(EncodeHintValue::ForceCodeSet(codeSetHint)) = hints.get(&EncodeHintType::FORCE_CODE_SET) else { panic!("This must exist by checks previous")};
|
||||
match codeSetHint.as_str() {
|
||||
"A" => forcedCodeSet = CODE_CODE_A as i32,
|
||||
"B" => forcedCodeSet = CODE_CODE_B as i32,
|
||||
"C" => forcedCodeSet = CODE_CODE_C as i32,
|
||||
_ => {
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Unsupported code set hint: {}",
|
||||
codeSetHint
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check content
|
||||
for ch in contents.chars() {
|
||||
let c = ch as u32;
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// char c = contents.charAt(i);
|
||||
// check for non ascii characters that are not special GS1 characters
|
||||
match ch {
|
||||
// special function characters
|
||||
ESCAPE_FNC_1 | ESCAPE_FNC_2 | ESCAPE_FNC_3 | ESCAPE_FNC_4 => {}
|
||||
// non ascii characters
|
||||
_ => {
|
||||
if c > 127 {
|
||||
// no full Latin-1 character set available at the moment
|
||||
// shift and manual code change are not supported
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Bad character in input: ASCII value={}",
|
||||
c
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// check characters for compatibility with forced code set
|
||||
const CODE_CODE_A_I32: i32 = CODE_CODE_A as i32;
|
||||
const CODE_CODE_B_I32: i32 = CODE_CODE_B as i32;
|
||||
const CODE_CODE_C_I32: i32 = CODE_CODE_C as i32;
|
||||
match forcedCodeSet {
|
||||
CODE_CODE_A_I32 =>
|
||||
// allows no ascii above 95 (no lower caps, no special symbols)
|
||||
{
|
||||
if c > 95 && c <= 127 {
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Bad character in input for forced code set A: ASCII value={}",
|
||||
c
|
||||
)));
|
||||
}
|
||||
}
|
||||
CODE_CODE_B_I32 =>
|
||||
// allows no ascii below 32 (terminal symbols)
|
||||
{
|
||||
if c <= 32 {
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Bad character in input for forced code set B: ASCII value={}",
|
||||
c
|
||||
)));
|
||||
}
|
||||
}
|
||||
CODE_CODE_C_I32 =>
|
||||
// allows only numbers and no FNC 2/3/4
|
||||
{
|
||||
if c < 48
|
||||
|| (c > 57 && c <= 127)
|
||||
|| ch == ESCAPE_FNC_2
|
||||
|| ch == ESCAPE_FNC_3
|
||||
|| ch == ESCAPE_FNC_4
|
||||
{
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Bad character in input for forced code set C: ASCII value={}",
|
||||
c
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(forcedCodeSet)
|
||||
}
|
||||
|
||||
fn encodeFast(contents: &str, forcedCodeSet: i32) -> Result<Vec<bool>, Exceptions> {
|
||||
let length = contents.chars().count();
|
||||
|
||||
let mut patterns: Vec<Vec<usize>> = Vec::new(); //new ArrayList<>(); // temporary storage for patterns
|
||||
let mut checkSum = 0;
|
||||
let mut checkWeight = 1;
|
||||
let mut codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
|
||||
let mut position = 0; // position in contents
|
||||
|
||||
while position < length {
|
||||
//Select code to use
|
||||
let newCodeSet = if forcedCodeSet == -1 {
|
||||
chooseCode(contents, position, codeSet)
|
||||
} else {
|
||||
forcedCodeSet as usize // THIS IS RISKY
|
||||
};
|
||||
|
||||
//Get the pattern index
|
||||
let mut patternIndex: isize;
|
||||
if newCodeSet == codeSet {
|
||||
// Encode the current character
|
||||
// First handle escapes
|
||||
match contents.chars().nth(position).unwrap() {
|
||||
ESCAPE_FNC_1 => patternIndex = CODE_FNC_1 as isize,
|
||||
ESCAPE_FNC_2 => patternIndex = CODE_FNC_2 as isize,
|
||||
ESCAPE_FNC_3 => patternIndex = CODE_FNC_3 as isize,
|
||||
ESCAPE_FNC_4 => {
|
||||
if codeSet == CODE_CODE_A {
|
||||
patternIndex = CODE_FNC_4_A as isize;
|
||||
} else {
|
||||
patternIndex = CODE_FNC_4_B as isize;
|
||||
}
|
||||
}
|
||||
_ =>
|
||||
// Then handle normal characters otherwise
|
||||
{
|
||||
match codeSet as usize {
|
||||
CODE_CODE_A => {
|
||||
patternIndex =
|
||||
contents.chars().nth(position).unwrap() as isize - ' ' as isize;
|
||||
if patternIndex < 0 {
|
||||
// everything below a space character comes behind the underscore in the code patterns table
|
||||
patternIndex += '`' as isize;
|
||||
}
|
||||
}
|
||||
CODE_CODE_B => {
|
||||
patternIndex =
|
||||
contents.chars().nth(position).unwrap() as isize - ' ' as isize
|
||||
}
|
||||
_ => {
|
||||
// CODE_CODE_C
|
||||
if position + 1 == length {
|
||||
// this is the last character, but the encoding is C, which always encodes two characers
|
||||
return Err(Exceptions::IllegalArgumentException(
|
||||
"Bad number of characters for digit only encoding.".to_owned(),
|
||||
));
|
||||
}
|
||||
patternIndex =
|
||||
contents[position..position + 1].parse::<isize>().unwrap();
|
||||
// patternIndex = Integer.parseInt(contents.substring(position, position + 2));
|
||||
position += 1;
|
||||
} // Also incremented below
|
||||
}
|
||||
}
|
||||
}
|
||||
position += 1;
|
||||
} else {
|
||||
// Should we change the current code?
|
||||
// Do we have a code set?
|
||||
if codeSet == 0 {
|
||||
// No, we don't have a code set
|
||||
match newCodeSet as usize {
|
||||
CODE_CODE_A => patternIndex = CODE_START_A as isize,
|
||||
CODE_CODE_B => patternIndex = CODE_START_B as isize,
|
||||
_ => patternIndex = CODE_START_C as isize,
|
||||
}
|
||||
} else {
|
||||
// Yes, we have a code set
|
||||
patternIndex = newCodeSet as isize;
|
||||
}
|
||||
codeSet = newCodeSet;
|
||||
}
|
||||
|
||||
// Get the pattern
|
||||
patterns.push(
|
||||
code_128_reader::CODE_PATTERNS[patternIndex as usize]
|
||||
.iter()
|
||||
.map(|x| *x as usize)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// Compute checksum
|
||||
checkSum += patternIndex * checkWeight;
|
||||
if position != 0 {
|
||||
checkWeight += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(produceRXingResult(&mut patterns, checkSum as usize))
|
||||
}
|
||||
|
||||
fn produceRXingResult(patterns: &mut Vec<Vec<usize>>, checkSum: usize) -> Vec<bool> {
|
||||
// Compute and append checksum
|
||||
let mut checkSum = checkSum;
|
||||
checkSum %= 103;
|
||||
patterns.push(
|
||||
code_128_reader::CODE_PATTERNS[checkSum]
|
||||
.iter()
|
||||
.map(|x| *x as usize)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// Append stop code
|
||||
patterns.push(
|
||||
code_128_reader::CODE_PATTERNS[CODE_STOP]
|
||||
.iter()
|
||||
.map(|x| *x as usize)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// Compute code width
|
||||
let mut codeWidth = 0_usize;
|
||||
for pattern in &mut *patterns {
|
||||
// for (int[] pattern : patterns) {
|
||||
for width in pattern {
|
||||
// for (int width : pattern) {
|
||||
codeWidth += *width as usize;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute result
|
||||
let mut result = vec![false; codeWidth];
|
||||
let mut pos = 0;
|
||||
for pattern in patterns {
|
||||
// for (int[] pattern : patterns) {
|
||||
pos += Code128Writer::appendPattern(&mut result, pos, pattern, true) as usize;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn findCType(value: &str, start: usize) -> CType {
|
||||
let last = value.chars().count();
|
||||
if start >= last {
|
||||
return CType::UNCODABLE;
|
||||
}
|
||||
let c = value.chars().nth(start).unwrap();
|
||||
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;
|
||||
}
|
||||
let c = value.chars().nth(start + 1).unwrap();
|
||||
if c < '0' || c > '9' {
|
||||
return CType::ONE_DIGIT;
|
||||
}
|
||||
return CType::TWO_DIGITS;
|
||||
}
|
||||
|
||||
fn chooseCode(value: &str, start: usize, oldCode: usize) -> usize {
|
||||
let mut lookahead = findCType(value, start);
|
||||
if lookahead == CType::ONE_DIGIT {
|
||||
if oldCode == CODE_CODE_A {
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
if lookahead == CType::UNCODABLE {
|
||||
if start < value.chars().count() {
|
||||
let c = value.chars().nth(start).unwrap();
|
||||
if c < ' '
|
||||
|| (oldCode == 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;
|
||||
}
|
||||
}
|
||||
return CODE_CODE_B; // no choice
|
||||
}
|
||||
if oldCode == CODE_CODE_A && lookahead == CType::FNC_1 {
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
if oldCode == CODE_CODE_C {
|
||||
// can continue in code C
|
||||
return CODE_CODE_C;
|
||||
}
|
||||
if oldCode == CODE_CODE_B {
|
||||
if lookahead == CType::FNC_1 {
|
||||
return CODE_CODE_B; // can continue in code B
|
||||
}
|
||||
// Seen two consecutive digits, see what follows
|
||||
lookahead = findCType(value, start + 2);
|
||||
if lookahead == CType::UNCODABLE || lookahead == CType::ONE_DIGIT {
|
||||
return CODE_CODE_B; // not worth switching now
|
||||
}
|
||||
if lookahead == CType::FNC_1 {
|
||||
// two digits, then FNC_1...
|
||||
lookahead = findCType(value, start + 3);
|
||||
if lookahead == CType::TWO_DIGITS {
|
||||
// then two more digits, switch
|
||||
return CODE_CODE_C;
|
||||
} else {
|
||||
return CODE_CODE_B; // otherwise not worth switching
|
||||
}
|
||||
}
|
||||
// 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 = start + 4;
|
||||
let mut lookahead = findCType(value, index);
|
||||
while lookahead == CType::TWO_DIGITS {
|
||||
// while (lookahead = findCType(value, index)) == CType::TWO_DIGITS {
|
||||
index += 2;
|
||||
lookahead = findCType(value, index);
|
||||
}
|
||||
if lookahead == CType::ONE_DIGIT {
|
||||
// odd number of digits, switch later
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
return CODE_CODE_C; // even number of digits, switch now
|
||||
}
|
||||
// Here oldCode == 0, which means we are choosing the initial code
|
||||
if lookahead == CType::FNC_1 {
|
||||
// ignore FNC_1
|
||||
lookahead = findCType(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
|
||||
**/
|
||||
// struct MinimalEncoder {
|
||||
// memoizedCost:Vec<Vec<u32>>,
|
||||
// minPath:Vec<Vec<Latch>>,
|
||||
// }
|
||||
mod MinimalEncoder {
|
||||
use crate::{oned::code_128_reader, Exceptions};
|
||||
|
||||
use super::{
|
||||
produceRXingResult, CODE_CODE_A, CODE_CODE_B, CODE_CODE_C, CODE_FNC_1, CODE_FNC_2,
|
||||
CODE_FNC_3, CODE_FNC_4_A, CODE_FNC_4_B, CODE_START_A, CODE_START_B, CODE_START_C,
|
||||
ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Charset {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
NONE,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Latch {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
SHIFT,
|
||||
NONE,
|
||||
}
|
||||
|
||||
const A : &str = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{0000}\u{0001}\u{0002}/
|
||||
\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{0008}\u{0009}\n\u{000B}\u{000C}\r\u{000E}\u{000F}\u{0010}\u{0011}/
|
||||
\u{0012}\u{0013}\u{0014}\u{0015}\u{0016}\u{0017}\u{0018}\u{0019}\u{001A}\u{001B}\u{001C}\u{001D}\u{001E}\u{001F}/
|
||||
\u{00FF}";
|
||||
const B: &str =
|
||||
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr\
|
||||
stuvwxyz{|}~\u{007F}\u{00FF}";
|
||||
|
||||
const CODE_SHIFT: usize = 98;
|
||||
|
||||
pub fn encode(contents: &str) -> Result<Vec<bool>, Exceptions> {
|
||||
let length = contents.chars().count();
|
||||
let mut memoizedCost = vec![vec![0_u32; 4]; length]; //new int[4][contents.length()];
|
||||
let mut minPath = vec![vec![Latch::NONE; 4]; length]; //new Latch[4][contents.length()];
|
||||
|
||||
encode_with_start_position(contents, Charset::NONE, 0, &mut memoizedCost, &mut minPath)?;
|
||||
|
||||
let mut patterns: Vec<Vec<usize>> = Vec::new(); //new ArrayList<>();
|
||||
let mut checkSum = vec![0_usize]; //new int[] {0};
|
||||
let mut checkWeight = vec![1]; //new int[] {1};
|
||||
let mut charset = Charset::NONE;
|
||||
let mut i = 0;
|
||||
while i < length {
|
||||
// for i in 0..length {
|
||||
// for (int i = 0; i < length; i++) {
|
||||
let latch = minPath[charset.ordinal()][i];
|
||||
match latch {
|
||||
Latch::A => {
|
||||
charset = Charset::A;
|
||||
addPattern(
|
||||
&mut patterns,
|
||||
if i == 0 { CODE_START_A } else { CODE_CODE_A },
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
);
|
||||
}
|
||||
Latch::B => {
|
||||
charset = Charset::B;
|
||||
addPattern(
|
||||
&mut patterns,
|
||||
if i == 0 { CODE_START_B } else { CODE_CODE_B },
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
);
|
||||
}
|
||||
Latch::C => {
|
||||
charset = Charset::C;
|
||||
addPattern(
|
||||
&mut patterns,
|
||||
if i == 0 { CODE_START_C } else { CODE_CODE_C },
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
);
|
||||
}
|
||||
Latch::SHIFT => addPattern(
|
||||
&mut patterns,
|
||||
CODE_SHIFT,
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
),
|
||||
Latch::NONE => { /* skip */ }
|
||||
}
|
||||
if charset == Charset::C {
|
||||
if contents.chars().nth(i).unwrap() == ESCAPE_FNC_1 {
|
||||
addPattern(
|
||||
&mut patterns,
|
||||
CODE_FNC_1,
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
);
|
||||
} else {
|
||||
addPattern(
|
||||
&mut patterns,
|
||||
(contents[i..i + 2]).parse::<usize>().unwrap(),
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
);
|
||||
assert!(i + 1 < length); //the algorithm never leads to a single trailing digit in character set C
|
||||
if i + 1 < length {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// charset A or B
|
||||
let mut patternIndex = match contents.chars().nth(i).unwrap() {
|
||||
ESCAPE_FNC_1 => CODE_FNC_1,
|
||||
ESCAPE_FNC_2 => CODE_FNC_2,
|
||||
ESCAPE_FNC_3 => CODE_FNC_3,
|
||||
ESCAPE_FNC_4 => {
|
||||
if (charset == Charset::A && latch != Latch::SHIFT)
|
||||
|| (charset == Charset::B && latch == Latch::SHIFT)
|
||||
{
|
||||
CODE_FNC_4_A
|
||||
} else {
|
||||
CODE_FNC_4_B
|
||||
}
|
||||
}
|
||||
_ => contents.chars().nth(i).unwrap() as usize - ' ' as usize,
|
||||
} as isize;
|
||||
if (charset == Charset::A && latch != Latch::SHIFT)
|
||||
|| (charset == Charset::B && latch == Latch::SHIFT)
|
||||
{
|
||||
if patternIndex < 0 {
|
||||
patternIndex += '`' as isize;
|
||||
}
|
||||
}
|
||||
addPattern(
|
||||
&mut patterns,
|
||||
patternIndex as usize,
|
||||
&mut checkSum,
|
||||
&mut checkWeight,
|
||||
i,
|
||||
);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
// memoizedCost.clear();
|
||||
// minPath.clear();
|
||||
|
||||
Ok(produceRXingResult(&mut patterns, checkSum[0]))
|
||||
}
|
||||
|
||||
fn addPattern(
|
||||
patterns: &mut Vec<Vec<usize>>,
|
||||
patternIndex: usize,
|
||||
checkSum: &mut [usize],
|
||||
checkWeight: &mut [u32],
|
||||
position: usize,
|
||||
) {
|
||||
patterns.push(
|
||||
code_128_reader::CODE_PATTERNS[patternIndex]
|
||||
.iter()
|
||||
.map(|x| *x as usize)
|
||||
.collect(),
|
||||
);
|
||||
if position != 0 {
|
||||
checkWeight[0] += 1;
|
||||
}
|
||||
checkSum[0] += patternIndex * checkWeight[0] as usize;
|
||||
}
|
||||
|
||||
fn isDigit(c: char) -> bool {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
fn canEncode(contents: &str, charset: Charset, position: usize) -> bool {
|
||||
let c = contents.chars().nth(position).unwrap();
|
||||
match charset {
|
||||
Charset::A => {
|
||||
c == ESCAPE_FNC_1
|
||||
|| c == ESCAPE_FNC_2
|
||||
|| c == ESCAPE_FNC_3
|
||||
|| c == ESCAPE_FNC_4
|
||||
|| A.find(c).is_some()
|
||||
}
|
||||
Charset::B => {
|
||||
c == ESCAPE_FNC_1
|
||||
|| c == ESCAPE_FNC_2
|
||||
|| c == ESCAPE_FNC_3
|
||||
|| c == ESCAPE_FNC_4
|
||||
|| B.find(c).is_some()
|
||||
}
|
||||
Charset::C => {
|
||||
c == ESCAPE_FNC_1
|
||||
|| (position + 1 < contents.chars().count()
|
||||
&& isDigit(c)
|
||||
&& isDigit(contents.chars().nth(position + 1).unwrap()))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the string starting at position position starting with the character set charset
|
||||
**/
|
||||
fn encode_with_start_position(
|
||||
contents: &str,
|
||||
charset: Charset,
|
||||
position: usize,
|
||||
memoizedCost: &mut Vec<Vec<u32>>,
|
||||
minPath: &mut Vec<Vec<Latch>>,
|
||||
) -> Result<u32, Exceptions> {
|
||||
assert!(position < contents.chars().count());
|
||||
let mCost = memoizedCost[charset.ordinal()][position];
|
||||
if mCost > 0 {
|
||||
return Ok(mCost);
|
||||
}
|
||||
|
||||
let mut minCost = u32::MAX;
|
||||
let mut minLatch = Latch::NONE;
|
||||
let atEnd = position + 1 >= contents.chars().count();
|
||||
|
||||
let sets = [Charset::A, Charset::B];
|
||||
for i in 0..=1 {
|
||||
// for (int i = 0; i <= 1; i++) {
|
||||
if canEncode(contents, sets[i], position) {
|
||||
let mut cost = 1;
|
||||
let mut latch = Latch::NONE;
|
||||
if charset != sets[i] {
|
||||
cost += 1;
|
||||
latch = sets[i].into(); //Latch::valueOf(sets[i].toString());
|
||||
}
|
||||
if !atEnd {
|
||||
cost += encode_with_start_position(
|
||||
contents,
|
||||
sets[i],
|
||||
position + 1,
|
||||
memoizedCost,
|
||||
minPath,
|
||||
)?;
|
||||
}
|
||||
if cost < minCost {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
cost = 1;
|
||||
if charset == sets[(i + 1) % 2] {
|
||||
cost += 1;
|
||||
latch = Latch::SHIFT;
|
||||
if !atEnd {
|
||||
cost += encode_with_start_position(
|
||||
contents,
|
||||
charset,
|
||||
position + 1,
|
||||
memoizedCost,
|
||||
minPath,
|
||||
)?;
|
||||
}
|
||||
if cost < minCost {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if canEncode(contents, Charset::C, position) {
|
||||
let mut cost = 1;
|
||||
let mut latch = Latch::NONE;
|
||||
if charset != Charset::C {
|
||||
cost += 1;
|
||||
latch = Latch::C;
|
||||
}
|
||||
let advance = if contents.chars().nth(position).unwrap() == ESCAPE_FNC_1 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
};
|
||||
if position + advance < contents.chars().count() {
|
||||
cost += encode_with_start_position(
|
||||
contents,
|
||||
Charset::C,
|
||||
position + advance,
|
||||
memoizedCost,
|
||||
minPath,
|
||||
)?;
|
||||
}
|
||||
if cost < minCost {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
}
|
||||
if minCost == u32::MAX {
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Bad character in input: ASCII value={}",
|
||||
contents.chars().nth(position).unwrap_or('x')
|
||||
)));
|
||||
// throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position));
|
||||
}
|
||||
memoizedCost[charset.ordinal()][position] = minCost;
|
||||
minPath[charset.ordinal()][position] = minLatch;
|
||||
Ok(minCost)
|
||||
}
|
||||
|
||||
trait HasOrdinal {
|
||||
fn ordinal(&self) -> usize;
|
||||
}
|
||||
|
||||
impl HasOrdinal for Charset {
|
||||
fn ordinal(&self) -> usize {
|
||||
match self {
|
||||
Charset::A => 0,
|
||||
Charset::B => 1,
|
||||
Charset::C => 2,
|
||||
Charset::NONE => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl HasOrdinal for Latch {
|
||||
fn ordinal(&self) -> usize {
|
||||
match self {
|
||||
Latch::A => 0,
|
||||
Latch::B => 1,
|
||||
Latch::C => 2,
|
||||
Latch::SHIFT => 3,
|
||||
Latch::NONE => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Charset> for Latch {
|
||||
fn from(cs: Charset) -> Self {
|
||||
match cs {
|
||||
Charset::A => Latch::A,
|
||||
Charset::B => Latch::B,
|
||||
Charset::C => Latch::C,
|
||||
Charset::NONE => Latch::NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,109 +20,115 @@ use crate::BarcodeFormat;
|
||||
|
||||
use super::OneDimensionalCodeWriter;
|
||||
|
||||
|
||||
/**
|
||||
* This object renders a ITF code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||||
*/
|
||||
#[derive(OneDWriter)]
|
||||
pub struct ITFWriter;
|
||||
pub struct ITFWriter;
|
||||
|
||||
impl Default for ITFWriter {
|
||||
fn default() -> Self {
|
||||
Self { }
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl OneDimensionalCodeWriter for ITFWriter {
|
||||
fn encode_oned(&self, contents: &str) -> Result<Vec<bool>, Exceptions> {
|
||||
let length = contents.chars().count();
|
||||
if length % 2 != 0 {
|
||||
return Err(Exceptions::IllegalArgumentException("The length of the input should be even".to_owned()));
|
||||
}
|
||||
if length > 80 {
|
||||
return Err(Exceptions::IllegalArgumentException(format!("Requested contents should be less than 80 digits long, but got {}" , length)));
|
||||
}
|
||||
|
||||
Self::checkNumeric(contents)?;
|
||||
|
||||
let mut result = vec![false;9 + 9 * length];
|
||||
let mut pos = Self::appendPattern(&mut result, 0, &START_PATTERN, true) as usize;
|
||||
let mut i = 0;
|
||||
while i < length {
|
||||
// for (int i = 0; i < length; i += 2) {
|
||||
let one = contents.chars().nth(i).unwrap().to_digit(10).unwrap() as usize;
|
||||
let two = contents.chars().nth(i + 1).unwrap().to_digit(10).unwrap() as usize;
|
||||
let mut encoding = [0;10];//new int[10];
|
||||
for j in 0..5 {
|
||||
// for (int j = 0; j < 5; j++) {
|
||||
encoding[2 * j] = PATTERNS[one][j];
|
||||
encoding[2 * j + 1] = PATTERNS[two][j];
|
||||
let length = contents.chars().count();
|
||||
if length % 2 != 0 {
|
||||
return Err(Exceptions::IllegalArgumentException(
|
||||
"The length of the input should be even".to_owned(),
|
||||
));
|
||||
}
|
||||
if length > 80 {
|
||||
return Err(Exceptions::IllegalArgumentException(format!(
|
||||
"Requested contents should be less than 80 digits long, but got {}",
|
||||
length
|
||||
)));
|
||||
}
|
||||
pos += Self::appendPattern(&mut result, pos, &encoding, true) as usize;
|
||||
|
||||
i+=2;
|
||||
}
|
||||
Self::appendPattern(&mut result, pos, &END_PATTERN, true);
|
||||
|
||||
Ok( result)
|
||||
Self::checkNumeric(contents)?;
|
||||
|
||||
let mut result = vec![false; 9 + 9 * length];
|
||||
let mut pos = Self::appendPattern(&mut result, 0, &START_PATTERN, true) as usize;
|
||||
let mut i = 0;
|
||||
while i < length {
|
||||
// for (int i = 0; i < length; i += 2) {
|
||||
let one = contents.chars().nth(i).unwrap().to_digit(10).unwrap() as usize;
|
||||
let two = contents.chars().nth(i + 1).unwrap().to_digit(10).unwrap() as usize;
|
||||
let mut encoding = [0; 10]; //new int[10];
|
||||
for j in 0..5 {
|
||||
// for (int j = 0; j < 5; j++) {
|
||||
encoding[2 * j] = PATTERNS[one][j];
|
||||
encoding[2 * j + 1] = PATTERNS[two][j];
|
||||
}
|
||||
pos += Self::appendPattern(&mut result, pos, &encoding, true) as usize;
|
||||
|
||||
i += 2;
|
||||
}
|
||||
Self::appendPattern(&mut result, pos, &END_PATTERN, true);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
fn getSupportedWriteFormats(&self) -> Option<Vec<crate::BarcodeFormat>> {
|
||||
Some(vec![BarcodeFormat::ITF])
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const START_PATTERN : [usize;4]= [1, 1, 1, 1];
|
||||
const END_PATTERN :[usize;3]= [3, 1, 1];
|
||||
const START_PATTERN: [usize; 4] = [1, 1, 1, 1];
|
||||
const END_PATTERN: [usize; 3] = [3, 1, 1];
|
||||
|
||||
const W : usize= 3; // Pixel width of a 3x wide line
|
||||
const N :usize= 1; // Pixed width of a narrow line
|
||||
const W: usize = 3; // Pixel width of a 3x wide line
|
||||
const N: usize = 1; // Pixed width of a narrow line
|
||||
|
||||
// See ITFReader.PATTERNS
|
||||
// See ITFReader.PATTERNS
|
||||
|
||||
const PATTERNS : [[usize;5];10]= [
|
||||
[N, N, W, W, N], // 0
|
||||
[W, N, N, N, W], // 1
|
||||
[N, W, N, N, W], // 2
|
||||
[W, W, N, N, N], // 3
|
||||
[N, N, W, N, W], // 4
|
||||
[W, N, W, N, N], // 5
|
||||
[N, W, W, N, N], // 6
|
||||
[N, N, N, W, W], // 7
|
||||
[W, N, N, W, N], // 8
|
||||
[N, W, N, W, N] // 9
|
||||
];
|
||||
const PATTERNS: [[usize; 5]; 10] = [
|
||||
[N, N, W, W, N], // 0
|
||||
[W, N, N, N, W], // 1
|
||||
[N, W, N, N, W], // 2
|
||||
[W, W, N, N, N], // 3
|
||||
[N, N, W, N, W], // 4
|
||||
[W, N, W, N, N], // 5
|
||||
[N, W, W, N, N], // 6
|
||||
[N, N, N, W, W], // 7
|
||||
[W, N, N, W, N], // 8
|
||||
[N, W, N, W, N], // 9
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests {@link ITFWriter}.
|
||||
*/
|
||||
#[cfg(test)]
|
||||
mod ITFWriterTestCase {
|
||||
use crate::{Writer, BarcodeFormat, common::BitMatrixTestCase};
|
||||
use crate::{common::BitMatrixTestCase, BarcodeFormat, Writer};
|
||||
|
||||
use super::ITFWriter;
|
||||
|
||||
#[test]
|
||||
fn testEncode() {
|
||||
doTest(
|
||||
"00123456789012",
|
||||
"0000010101010111000111000101110100010101110001110111010001010001110100011\
|
||||
100010101000101011100011101011101000111000101110100010101110001110100000",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn testEncode() {
|
||||
doTest("00123456789012",
|
||||
"0000010101010111000111000101110100010101110001110111010001010001110100011\
|
||||
100010101000101011100011101011101000111000101110100010101110001110100000");
|
||||
}
|
||||
|
||||
fn doTest( input:&str, expected:&str) {
|
||||
let result = ITFWriter::default().encode(input, &BarcodeFormat::ITF, 0, 0).expect("encode");
|
||||
assert_eq!(expected, BitMatrixTestCase::matrix_to_string(&result));
|
||||
}
|
||||
|
||||
//@Test(expected = IllegalArgumentException.class)
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn testEncodeIllegalCharacters() {
|
||||
ITFWriter::default().encode("00123456789abc", &BarcodeFormat::ITF, 0, 0).expect("should fail");
|
||||
}
|
||||
fn doTest(input: &str, expected: &str) {
|
||||
let result = ITFWriter::default()
|
||||
.encode(input, &BarcodeFormat::ITF, 0, 0)
|
||||
.expect("encode");
|
||||
assert_eq!(expected, BitMatrixTestCase::matrix_to_string(&result));
|
||||
}
|
||||
|
||||
//@Test(expected = IllegalArgumentException.class)
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn testEncodeIllegalCharacters() {
|
||||
ITFWriter::default()
|
||||
.encode("00123456789abc", &BarcodeFormat::ITF, 0, 0)
|
||||
.expect("should fail");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,4 +63,7 @@ mod code_93_writer;
|
||||
pub use code_93_writer::*;
|
||||
|
||||
mod itf_writer;
|
||||
pub use itf_writer::*;
|
||||
pub use itf_writer::*;
|
||||
|
||||
mod code_128_writer;
|
||||
pub use code_128_writer::*;
|
||||
|
||||
Reference in New Issue
Block a user