Code128Writer.java (zxing-zxing-3.4.1) | : | Code128Writer.java (zxing-zxing-3.5.0) | ||
---|---|---|---|---|
skipping to change at line 20 | skipping to change at line 20 | |||
* Unless required by applicable law or agreed to in writing, software | * Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | * distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | |||
* limitations under the License. | * limitations under the License. | |||
*/ | */ | |||
package com.google.zxing.oned; | package com.google.zxing.oned; | |||
import com.google.zxing.BarcodeFormat; | import com.google.zxing.BarcodeFormat; | |||
import com.google.zxing.EncodeHintType; | ||||
import com.google.zxing.common.BitMatrix; | import com.google.zxing.common.BitMatrix; | |||
import java.util.ArrayList; | import java.util.ArrayList; | |||
import java.util.Collection; | import java.util.Collection; | |||
import java.util.Collections; | import java.util.Collections; | |||
import java.util.Map; | ||||
/** | /** | |||
* This object renders a CODE128 code as a {@link BitMatrix}. | * This object renders a CODE128 code as a {@link BitMatrix}. | |||
* | * | |||
* @author erik.barbara@gmail.com (Erik Barbara) | * @author erik.barbara@gmail.com (Erik Barbara) | |||
*/ | */ | |||
public final class Code128Writer extends OneDimensionalCodeWriter { | public final class Code128Writer extends OneDimensionalCodeWriter { | |||
private static final int CODE_START_A = 103; | private static final int CODE_START_A = 103; | |||
private static final int CODE_START_B = 104; | private static final int CODE_START_B = 104; | |||
skipping to change at line 68 | skipping to change at line 70 | |||
FNC_1 | FNC_1 | |||
} | } | |||
@Override | @Override | |||
protected Collection<BarcodeFormat> getSupportedWriteFormats() { | protected Collection<BarcodeFormat> getSupportedWriteFormats() { | |||
return Collections.singleton(BarcodeFormat.CODE_128); | return Collections.singleton(BarcodeFormat.CODE_128); | |||
} | } | |||
@Override | @Override | |||
public boolean[] encode(String contents) { | 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(EncodeHintTyp | ||||
e.CODE128_COMPACT) && | ||||
Boolean.parseBoolean(hints.get(EncodeHintType.CODE128_COMPACT).toString( | ||||
)); | ||||
return hasCompactionHint ? new MinimalEncoder().encode(contents) : encodeFas | ||||
t(contents, forcedCodeSet); | ||||
} | ||||
private static int check(String contents, Map<EncodeHintType,?> hints) { | ||||
int length = contents.length(); | int length = contents.length(); | |||
// Check length | // Check length | |||
if (length < 1 || length > 80) { | if (length < 1 || length > 80) { | |||
throw new IllegalArgumentException( | throw new IllegalArgumentException( | |||
"Contents length should be between 1 and 80 characters, but got " + le ngth); | "Contents length should be between 1 and 80 characters, but got " + le ngth); | |||
} | } | |||
// 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: " + cod | ||||
eSetHint); | ||||
} | ||||
} | ||||
// Check content | // Check content | |||
for (int i = 0; i < length; i++) { | for (int i = 0; i < length; i++) { | |||
char c = contents.charAt(i); | char c = contents.charAt(i); | |||
// check for non ascii characters that are not special GS1 characters | ||||
switch (c) { | switch (c) { | |||
// special function characters | ||||
case ESCAPE_FNC_1: | case ESCAPE_FNC_1: | |||
case ESCAPE_FNC_2: | case ESCAPE_FNC_2: | |||
case ESCAPE_FNC_3: | case ESCAPE_FNC_3: | |||
case ESCAPE_FNC_4: | case ESCAPE_FNC_4: | |||
break; | break; | |||
// non ascii characters | ||||
default: | default: | |||
if (c > 127) { | if (c > 127) { | |||
// support for FNC4 isn't implemented, no full Latin-1 character set | // no full Latin-1 character set available at the moment | |||
available at the moment | // shift and manual code change are not supported | |||
throw new IllegalArgumentException("Bad character in input: " + c); | throw new IllegalArgumentException("Bad character in input: ASCII va | |||
lue=" + (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 force | ||||
d 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 force | ||||
d 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 force | ||||
d 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 pat terns | Collection<int[]> patterns = new ArrayList<>(); // temporary storage for pat terns | |||
int checkSum = 0; | int checkSum = 0; | |||
int checkWeight = 1; | int checkWeight = 1; | |||
int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C) | int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C) | |||
int position = 0; // position in contents | int position = 0; // position in contents | |||
while (position < length) { | while (position < length) { | |||
//Select code to use | //Select code to use | |||
int newCodeSet = chooseCode(contents, position, codeSet); | int newCodeSet; | |||
if (forcedCodeSet == -1) { | ||||
newCodeSet = chooseCode(contents, position, codeSet); | ||||
} else { | ||||
newCodeSet = forcedCodeSet; | ||||
} | ||||
//Get the pattern index | //Get the pattern index | |||
int patternIndex; | int patternIndex; | |||
if (newCodeSet == codeSet) { | if (newCodeSet == codeSet) { | |||
// Encode the current character | // Encode the current character | |||
// First handle escapes | // First handle escapes | |||
switch (contents.charAt(position)) { | switch (contents.charAt(position)) { | |||
case ESCAPE_FNC_1: | case ESCAPE_FNC_1: | |||
patternIndex = CODE_FNC_1; | patternIndex = CODE_FNC_1; | |||
break; | break; | |||
skipping to change at line 138 | skipping to change at line 210 | |||
if (patternIndex < 0) { | if (patternIndex < 0) { | |||
// everything below a space character comes behind the undersc ore in the code patterns table | // everything below a space character comes behind the undersc ore in the code patterns table | |||
patternIndex += '`'; | patternIndex += '`'; | |||
} | } | |||
break; | break; | |||
case CODE_CODE_B: | case CODE_CODE_B: | |||
patternIndex = contents.charAt(position) - ' '; | patternIndex = contents.charAt(position) - ' '; | |||
break; | break; | |||
default: | default: | |||
// CODE_CODE_C | // CODE_CODE_C | |||
if (position + 1 == length) { | ||||
// this is the last character, but the encoding is C, which al | ||||
ways encodes two characers | ||||
throw new IllegalArgumentException("Bad number of characters f | ||||
or digit only encoding."); | ||||
} | ||||
patternIndex = Integer.parseInt(contents.substring(position, pos ition + 2)); | patternIndex = Integer.parseInt(contents.substring(position, pos ition + 2)); | |||
position++; // Also incremented below | position++; // Also incremented below | |||
break; | break; | |||
} | } | |||
} | } | |||
position++; | position++; | |||
} else { | } else { | |||
// Should we change the current code? | // Should we change the current code? | |||
// Do we have a code set? | // Do we have a code set? | |||
if (codeSet == 0) { | if (codeSet == 0) { | |||
skipping to change at line 176 | skipping to change at line 252 | |||
// Get the pattern | // Get the pattern | |||
patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]); | patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]); | |||
// Compute checksum | // Compute checksum | |||
checkSum += patternIndex * checkWeight; | checkSum += patternIndex * checkWeight; | |||
if (position != 0) { | if (position != 0) { | |||
checkWeight++; | checkWeight++; | |||
} | } | |||
} | } | |||
return produceResult(patterns, checkSum); | ||||
} | ||||
static boolean[] produceResult(Collection<int[]> patterns, int checkSum) { | ||||
// Compute and append checksum | // Compute and append checksum | |||
checkSum %= 103; | checkSum %= 103; | |||
patterns.add(Code128Reader.CODE_PATTERNS[checkSum]); | patterns.add(Code128Reader.CODE_PATTERNS[checkSum]); | |||
// Append stop code | // Append stop code | |||
patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]); | patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]); | |||
// Compute code width | // Compute code width | |||
int codeWidth = 0; | int codeWidth = 0; | |||
for (int[] pattern : patterns) { | for (int[] pattern : patterns) { | |||
skipping to change at line 227 | skipping to change at line 306 | |||
c = value.charAt(start + 1); | c = value.charAt(start + 1); | |||
if (c < '0' || c > '9') { | if (c < '0' || c > '9') { | |||
return CType.ONE_DIGIT; | return CType.ONE_DIGIT; | |||
} | } | |||
return CType.TWO_DIGITS; | return CType.TWO_DIGITS; | |||
} | } | |||
private static int chooseCode(CharSequence value, int start, int oldCode) { | private static int chooseCode(CharSequence value, int start, int oldCode) { | |||
CType lookahead = findCType(value, start); | CType lookahead = findCType(value, start); | |||
if (lookahead == CType.ONE_DIGIT) { | if (lookahead == CType.ONE_DIGIT) { | |||
if (oldCode == CODE_CODE_A) { | if (oldCode == CODE_CODE_A) { | |||
return CODE_CODE_A; | return CODE_CODE_A; | |||
} | } | |||
return CODE_CODE_B; | return CODE_CODE_B; | |||
} | } | |||
if (lookahead == CType.UNCODABLE) { | if (lookahead == CType.UNCODABLE) { | |||
if (start < value.length()) { | if (start < value.length()) { | |||
char c = value.charAt(start); | char c = value.charAt(start); | |||
if (c < ' ' || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)))) { | 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 | // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 | |||
return CODE_CODE_A; | return CODE_CODE_A; | |||
} | } | |||
} | } | |||
return CODE_CODE_B; // no choice | return CODE_CODE_B; // no choice | |||
skipping to change at line 286 | skipping to change at line 365 | |||
// Here oldCode == 0, which means we are choosing the initial code | // Here oldCode == 0, which means we are choosing the initial code | |||
if (lookahead == CType.FNC_1) { // ignore FNC_1 | if (lookahead == CType.FNC_1) { // ignore FNC_1 | |||
lookahead = findCType(value, start + 1); | lookahead = findCType(value, start + 1); | |||
} | } | |||
if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C | if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C | |||
return CODE_CODE_C; | return CODE_CODE_C; | |||
} | } | |||
return CODE_CODE_B; | 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:;<=>?@ABCDEFGHIJKLMNOPQ | ||||
RSTUVWXYZ[\\]^_\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\u0 | ||||
01A\u001B\u001C\u001D\u001E\u001F" + | ||||
"\u00FF"; | ||||
static final String B = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQ | ||||
RSTUVWXYZ[\\]^_`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 trail | ||||
ing 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 produceResult(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 positio | ||||
n) { | ||||
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 charact | ||||
er 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; | ||||
} | ||||
} | ||||
} | } | |||
End of changes. 16 change blocks. | ||||
8 lines changed or deleted | 311 lines changed or added |