com.google.zxing.oned.Code128Writer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Core barcode encoding/decoding library
/*
* 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 [email protected] (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
// Results of minimal lookahead for code C
private enum CType {
UNCODABLE,
ONE_DIGIT,
TWO_DIGITS,
FNC_1
}
@Override
protected Collection getSupportedWriteFormats() {
return Collections.singleton(BarcodeFormat.CODE_128);
}
@Override
public boolean[] encode(String contents) {
return encode(contents, null);
}
@Override
protected boolean[] encode(String contents, Map 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 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 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 produceResult(patterns, checkSum);
}
static boolean[] produceResult(Collection 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 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) &&
patternIndex < 0) {
patternIndex += '`';
}
addPattern(patterns, patternIndex, checkSum, checkWeight, i);
}
}
memoizedCost = null;
minPath = null;
return produceResult(patterns, checkSum[0]);
}
private static void addPattern(Collection 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;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy