mirror of
https://github.com/zxing/zxing.git
synced 2025-01-12 11:47:26 -08:00
Introduce hint to force specific code set for Code-128 encoding (#1411)
* Introduce hint to force specific code set for Code-128 encoding * Address review comments * Make character check more readable; address review comments * Address review comments Co-authored-by: Gero Schwäricke <gero@schwaericke.email>
This commit is contained in:
parent
0d08915628
commit
d2027d0e0f
|
@ -115,4 +115,9 @@ public enum EncodeHintType {
|
||||||
* {@link String } value).
|
* {@link String } value).
|
||||||
*/
|
*/
|
||||||
GS1_FORMAT,
|
GS1_FORMAT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces which encoding will be used. Currently only used for Code-128 code sets (Type {@link String}). Valid values are "A", "B", "C".
|
||||||
|
*/
|
||||||
|
FORCE_CODE_SET,
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@
|
||||||
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}.
|
||||||
|
@ -65,27 +67,77 @@ public final class Code128Writer extends OneDimensionalCodeWriter {
|
||||||
|
|
||||||
@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 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 " + length);
|
"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
|
// 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 available at the moment
|
// no full Latin-1 character set available at the moment
|
||||||
throw new IllegalArgumentException("Bad character in input: " + c);
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<int[]> patterns = new ArrayList<>(); // temporary storage for patterns
|
Collection<int[]> patterns = new ArrayList<>(); // temporary storage for patterns
|
||||||
|
@ -96,7 +148,12 @@ public final class Code128Writer extends OneDimensionalCodeWriter {
|
||||||
|
|
||||||
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;
|
||||||
|
@ -135,6 +192,10 @@ public final class Code128Writer extends OneDimensionalCodeWriter {
|
||||||
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 always encodes two characers
|
||||||
|
throw new IllegalArgumentException("Bad number of characters for digit only encoding.");
|
||||||
|
}
|
||||||
patternIndex = Integer.parseInt(contents.substring(position, position + 2));
|
patternIndex = Integer.parseInt(contents.substring(position, position + 2));
|
||||||
position++; // Also incremented below
|
position++; // Also incremented below
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -33,6 +33,25 @@ import java.util.regex.Pattern;
|
||||||
public abstract class OneDimensionalCodeWriter implements Writer {
|
public abstract class OneDimensionalCodeWriter implements Writer {
|
||||||
private static final Pattern NUMERIC = Pattern.compile("[0-9]+");
|
private static final Pattern NUMERIC = Pattern.compile("[0-9]+");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the contents to boolean array expression of one-dimensional barcode.
|
||||||
|
* Start code and end code should be included in result, and side margins should not be included.
|
||||||
|
*
|
||||||
|
* @param contents barcode contents to encode
|
||||||
|
* @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
|
||||||
|
*/
|
||||||
|
public abstract boolean[] encode(String contents);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be overwritten if the encode requires to read the hints map. Otherwise it defaults to {@code encode}.
|
||||||
|
* @param contents barcode contents to encode
|
||||||
|
* @param hints encoding hints
|
||||||
|
* @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
|
||||||
|
*/
|
||||||
|
protected boolean[] encode(String contents, Map<EncodeHintType,?> hints) {
|
||||||
|
return encode(contents);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
|
public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
|
||||||
return encode(contents, format, width, height, null);
|
return encode(contents, format, width, height, null);
|
||||||
|
@ -70,7 +89,7 @@ public abstract class OneDimensionalCodeWriter implements Writer {
|
||||||
sidesMargin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
|
sidesMargin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean[] code = encode(contents);
|
boolean[] code = encode(contents, hints);
|
||||||
return renderResult(code, width, height, sidesMargin);
|
return renderResult(code, width, height, sidesMargin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,14 +154,5 @@ public abstract class OneDimensionalCodeWriter implements Writer {
|
||||||
// This seems like a decent idea for a default for all formats.
|
// This seems like a decent idea for a default for all formats.
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode the contents to boolean array expression of one-dimensional barcode.
|
|
||||||
* Start code and end code should be included in result, and side margins should not be included.
|
|
||||||
*
|
|
||||||
* @param contents barcode contents to encode
|
|
||||||
* @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
|
|
||||||
*/
|
|
||||||
public abstract boolean[] encode(String contents);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,16 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
import com.google.zxing.Writer;
|
import com.google.zxing.Writer;
|
||||||
import com.google.zxing.WriterException;
|
import com.google.zxing.WriterException;
|
||||||
import com.google.zxing.common.BitArray;
|
import com.google.zxing.common.BitArray;
|
||||||
import com.google.zxing.common.BitMatrix;
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link Code128Writer}.
|
* Tests {@link Code128Writer}.
|
||||||
*/
|
*/
|
||||||
|
@ -151,4 +155,82 @@ public class Code128WriterTestCase extends Assert {
|
||||||
String actualRoundtripResultText = rtResult.getText();
|
String actualRoundtripResultText = rtResult.getText();
|
||||||
assertEquals(toEncode, actualRoundtripResultText);
|
assertEquals(toEncode, actualRoundtripResultText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetABadCharacter() throws Exception {
|
||||||
|
// Lower case characters should not be accepted when the code set is forced to A.
|
||||||
|
String toEncode = "ASDFx0123";
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "A");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetBBadCharacter() throws Exception {
|
||||||
|
String toEncode = "ASdf\00123"; // \0 (ascii value 0)
|
||||||
|
// Characters with ASCII value below 32 should not be accepted when the code set is forced to B.
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "B");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetCBadCharactersNonNum() throws Exception {
|
||||||
|
String toEncode = "123a5678";
|
||||||
|
// Non-digit characters should not be accepted when the code set is forced to C.
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "C");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetCBadCharactersFncCode() throws Exception {
|
||||||
|
String toEncode = "123\u00f2a678";
|
||||||
|
// Function codes other than 1 should not be accepted when the code set is forced to C.
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "C");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetCWrongAmountOfDigits() throws Exception {
|
||||||
|
String toEncode = "123456789";
|
||||||
|
// An uneven amount of digits should not be accepted when the code set is forced to C.
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "C");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetA() throws Exception {
|
||||||
|
String toEncode = "AB123";
|
||||||
|
// would default to B "A" "B" "1" "2" "3" check digit 10
|
||||||
|
String expected = QUIET_SPACE + START_CODE_A + "10100011000" + "10001011000" + "10011100110" + "11001110010" + "11001011100" + "11001000100" + STOP + QUIET_SPACE;
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "A");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
|
||||||
|
String actual = BitMatrixTestCase.matrixToString(result);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeWithForcedCodeSetFailureCodeSetB() throws Exception {
|
||||||
|
String toEncode = "1234";
|
||||||
|
// would default to C "1" "2" "3" "4" check digit 88
|
||||||
|
String expected = QUIET_SPACE + START_CODE_B + "10011100110" + "11001110010" + "11001011100" + "11001001110" + "11110010010" + STOP + QUIET_SPACE;
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||||
|
hints.put(EncodeHintType.FORCE_CODE_SET, "B");
|
||||||
|
BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0, hints);
|
||||||
|
|
||||||
|
String actual = BitMatrixTestCase.matrixToString(result);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue