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:
gschwaer 2021-07-29 22:28:21 +02:00 committed by GitHub
parent 0d08915628
commit d2027d0e0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 171 additions and 13 deletions

View file

@ -115,4 +115,9 @@ public enum EncodeHintType {
* {@link String } value).
*/
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,
}

View file

@ -17,11 +17,13 @@
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}.
@ -65,27 +67,77 @@ public final class Code128Writer extends OneDimensionalCodeWriter {
@Override
public boolean[] encode(String contents) {
return encode(contents, null);
}
@Override
protected boolean[] encode(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) {
// support for FNC4 isn't implemented, no full Latin-1 character set available at the moment
throw new IllegalArgumentException("Bad character in input: " + c);
// 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;
}
}
Collection<int[]> patterns = new ArrayList<>(); // temporary storage for patterns
@ -96,7 +148,12 @@ public final class Code128Writer extends OneDimensionalCodeWriter {
while (position < length) {
//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
int patternIndex;
@ -135,6 +192,10 @@ public final class Code128Writer extends OneDimensionalCodeWriter {
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;

View file

@ -33,6 +33,25 @@ import java.util.regex.Pattern;
public abstract class OneDimensionalCodeWriter implements Writer {
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
public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
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());
}
boolean[] code = encode(contents);
boolean[] code = encode(contents, hints);
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.
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);
}

View file

@ -22,12 +22,16 @@ import org.junit.Before;
import org.junit.Test;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.Result;
import com.google.zxing.Writer;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
import java.util.Map;
import java.util.EnumMap;
/**
* Tests {@link Code128Writer}.
*/
@ -151,4 +155,82 @@ public class Code128WriterTestCase extends Assert {
String actualRoundtripResultText = rtResult.getText();
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);
}
}