Add support for multi-eci encoding for PDF417 (#1506)

* - Added multi-eci encoding for PDF417
- Fixed issue that the PDF417 decoder incorrectly decoded input with a leading ECI and no explicit latch to TEXT encoding
This commit is contained in:
AlexGeller1 2022-03-05 15:07:37 +01:00 committed by GitHub
parent 4bd257e8c5
commit 92854d4a55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 866 additions and 336 deletions

View file

@ -103,6 +103,16 @@ public enum EncodeHintType {
*/
PDF417_DIMENSIONS,
/**
* Specifies whether to automatically insert ECIs when encoding PDF417 (type {@link Boolean}, or "true" or "false"
* {@link String} value).
* Please note that in that case, the most compact character encoding is chosen for characters in
* the input that are not in the ISO-8859-1 character set. Based on experience, some scanners do not
* support encodings like cp-1256 (Arabic). In such cases the encoding can be forced to UTF-8 by
* means of the {@link #CHARACTER_SET} encoding hint.
*/
PDF417_AUTO_ECI,
/**
* Specifies the required number of layers for an Aztec code.
* A negative number (-1, -2, -3, -4) specifies a compact Aztec code.

View file

@ -0,0 +1,107 @@
/*
* Copyright 2021 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.common;
/**
* Interface to navigate a sequence of ECIs and bytes.
*
* @author Alex Geller
*/
public interface ECIInput {
/**
* Returns the length of this input. The length is the number
* of {@code byte}s in or ECIs in the sequence.
*
* @return the number of {@code char}s in this sequence
*/
int length();
/**
* Returns the {@code byte} value at the specified index. An index ranges from zero
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
* index zero, the next at index one, and so on, as for array
* indexing.
*
* @param index the index of the {@code byte} value to be returned
*
* @return the specified {@code byte} value as character or the FNC1 character
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
* @throws IllegalArgumentException
* if the value at the {@code index} argument is an ECI (@see #isECI)
*/
char charAt(int index);
/**
* Returns a {@code CharSequence} that is a subsequence of this sequence.
* The subsequence starts with the {@code char} value at the specified index and
* ends with the {@code char} value at index {@code end - 1}. The length
* (in {@code char}s) of the
* returned sequence is {@code end - start}, so if {@code start == end}
* then an empty sequence is returned.
*
* @param start the start index, inclusive
* @param end the end index, exclusive
*
* @return the specified subsequence
*
* @throws IndexOutOfBoundsException
* if {@code start} or {@code end} are negative,
* if {@code end} is greater than {@code length()},
* or if {@code start} is greater than {@code end}
* @throws IllegalArgumentException
* if a value in the range {@code start}-{@code end} is an ECI (@see #isECI)
*/
CharSequence subSequence(int start, int end);
/**
* Determines if a value is an ECI
*
* @param index the index of the value
*
* @return true if the value at position {@code index} is an ECI
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
*/
boolean isECI(int index);
/**
* Returns the {@code int} ECI value at the specified index. An index ranges from zero
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
* index zero, the next at index one, and so on, as for array
* indexing.
*
* @param index the index of the {@code int} value to be returned
*
* @return the specified {@code int} ECI value.
* The ECI specified the encoding of all bytes with a higher index until the
* next ECI or until the end of the input if no other ECI follows.
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
* @throws IllegalArgumentException
* if the value at the {@code index} argument is not an ECI (@see #isECI)
*/
int getECIValue(int index);
boolean haveNCharacters(int index, int n);
}

View file

@ -0,0 +1,325 @@
/*
* Copyright 2021 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.common;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* Class that converts a character string into a sequence of ECIs and bytes
*
* The implementation uses the Dijkstra algorithm to produce minimal encodings
*
* @author Alex Geller
*/
public class MinimalECIInput implements ECIInput {
private static final int COST_PER_ECI = 3; // approximated (latch + 2 codewords)
private final int[] bytes;
private final int fnc1;
/**
* Constructs a minimal input
*
* @param stringToEncode the character string to encode
* @param priorityCharset The preferred {@link Charset}. When the value of the argument is null, the algorithm
* chooses charsets that leads to a minimal representation. Otherwise the algorithm will use the priority
* charset to encode any character in the input that can be encoded by it if the charset is among the
* supported charsets.
* @param fnc1 denotes the character in the input that represents the FNC1 character or -1 if this is not GS1
* input.
*/
public MinimalECIInput(String stringToEncode, Charset priorityCharset, int fnc1) {
this.fnc1 = fnc1;
ECIEncoderSet encoderSet = new ECIEncoderSet(stringToEncode, priorityCharset, fnc1);
if (encoderSet.length() == 1) { //optimization for the case when all can be encoded without ECI in ISO-8859-1
bytes = new int[stringToEncode.length()];
for (int i = 0; i < bytes.length; i++) {
char c = stringToEncode.charAt(i);
bytes[i] = c == fnc1 ? 1000 : (int) c;
}
} else {
bytes = encodeMinimally(stringToEncode, encoderSet, fnc1);
}
}
public int getFNC1Character() {
return fnc1;
}
/**
* Returns the length of this input. The length is the number
* of {@code byte}s, FNC1 characters or ECIs in the sequence.
*
* @return the number of {@code char}s in this sequence
*/
public int length() {
return bytes.length;
}
public boolean haveNCharacters(int index, int n) {
if (index + n - 1 >= bytes.length) {
return false;
}
for (int i = 0; i < n; i++) {
if (isECI(index + i)) {
return false;
}
}
return true;
}
/**
* Returns the {@code byte} value at the specified index. An index ranges from zero
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
* index zero, the next at index one, and so on, as for array
* indexing.
*
* @param index the index of the {@code byte} value to be returned
*
* @return the specified {@code byte} value as character or the FNC1 character
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
* @throws IllegalArgumentException
* if the value at the {@code index} argument is an ECI (@see #isECI)
*/
public char charAt(int index) {
if (index < 0 || index >= length()) {
throw new IndexOutOfBoundsException("" + index);
}
if (isECI(index)) {
throw new IllegalArgumentException("value at " + index + " is not a character but an ECI");
}
return isFNC1(index) ? (char) fnc1 : (char) bytes[index];
}
/**
* Returns a {@code CharSequence} that is a subsequence of this sequence.
* The subsequence starts with the {@code char} value at the specified index and
* ends with the {@code char} value at index {@code end - 1}. The length
* (in {@code char}s) of the
* returned sequence is {@code end - start}, so if {@code start == end}
* then an empty sequence is returned.
*
* @param start the start index, inclusive
* @param end the end index, exclusive
*
* @return the specified subsequence
*
* @throws IndexOutOfBoundsException
* if {@code start} or {@code end} are negative,
* if {@code end} is greater than {@code length()},
* or if {@code start} is greater than {@code end}
* @throws IllegalArgumentException
* if a value in the range {@code start}-{@code end} is an ECI (@see #isECI)
*/
public CharSequence subSequence(int start, int end) {
if (start < 0 || start > end || end > length()) {
throw new IndexOutOfBoundsException("" + start);
}
StringBuilder result = new StringBuilder();
for (int i = start; i < end; i++) {
if (isECI(i)) {
throw new IllegalArgumentException("value at " + i + " is not a character but an ECI");
}
result.append(charAt(i));
}
return result;
}
/**
* Determines if a value is an ECI
*
* @param index the index of the value
*
* @return true if the value at position {@code index} is an ECI
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
*/
public boolean isECI(int index) {
if (index < 0 || index >= length()) {
throw new IndexOutOfBoundsException("" + index);
}
return bytes[index] > 255 && bytes[index] <= 999;
}
/**
* Determines if a value is the FNC1 character
*
* @param index the index of the value
*
* @return true if the value at position {@code index} is the FNC1 character
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
*/
public boolean isFNC1(int index) {
if (index < 0 || index >= length()) {
throw new IndexOutOfBoundsException("" + index);
}
return bytes[index] == 1000;
}
/**
* Returns the {@code int} ECI value at the specified index. An index ranges from zero
* to {@code length() - 1}. The first {@code byte} value of the sequence is at
* index zero, the next at index one, and so on, as for array
* indexing.
*
* @param index the index of the {@code int} value to be returned
*
* @return the specified {@code int} ECI value.
* The ECI specified the encoding of all bytes with a higher index until the
* next ECI or until the end of the input if no other ECI follows.
*
* @throws IndexOutOfBoundsException
* if the {@code index} argument is negative or not less than
* {@code length()}
* @throws IllegalArgumentException
* if the value at the {@code index} argument is not an ECI (@see #isECI)
*/
public int getECIValue(int index) {
if (index < 0 || index >= length()) {
throw new IndexOutOfBoundsException("" + index);
}
if (!isECI(index)) {
throw new IllegalArgumentException("value at " + index + " is not an ECI but a character");
}
return bytes[index] - 256;
}
static void addEdge(InputEdge[][] edges, int to, InputEdge edge) {
if (edges[to][edge.encoderIndex] == null ||
edges[to][edge.encoderIndex].cachedTotalSize > edge.cachedTotalSize) {
edges[to][edge.encoderIndex] = edge;
}
}
static void addEdges(String stringToEncode,
ECIEncoderSet encoderSet,
InputEdge[][] edges,
int from,
InputEdge previous,
int fnc1) {
char ch = stringToEncode.charAt(from);
int start = 0;
int end = encoderSet.length();
if (encoderSet.getPriorityEncoderIndex() >= 0 && (ch == fnc1 || encoderSet.canEncode(ch,
encoderSet.getPriorityEncoderIndex()))) {
start = encoderSet.getPriorityEncoderIndex();
end = start + 1;
}
for (int i = start; i < end; i++) {
if (ch == fnc1 || encoderSet.canEncode(ch,i)) {
addEdge(edges, from + 1, new InputEdge(ch, encoderSet, i, previous, fnc1));
}
}
}
static int[] encodeMinimally(String stringToEncode, ECIEncoderSet encoderSet, int fnc1) {
int inputLength = stringToEncode.length();
// Array that represents vertices. There is a vertex for every character and encoding.
InputEdge[][] edges = new InputEdge[inputLength + 1][encoderSet.length()];
addEdges(stringToEncode, encoderSet, edges, 0, null, fnc1);
for (int i = 1; i <= inputLength; i++) {
for (int j = 0; j < encoderSet.length(); j++) {
if (edges[i][j] != null && i < inputLength) {
addEdges(stringToEncode, encoderSet, edges, i, edges[i][j], fnc1);
}
}
//optimize memory by removing edges that have been passed.
for (int j = 0; j < encoderSet.length(); j++) {
edges[i - 1][j] = null;
}
}
int minimalJ = -1;
int minimalSize = Integer.MAX_VALUE;
for (int j = 0; j < encoderSet.length(); j++) {
if (edges[inputLength][j] != null) {
InputEdge edge = edges[inputLength][j];
if (edge.cachedTotalSize < minimalSize) {
minimalSize = edge.cachedTotalSize;
minimalJ = j;
}
}
}
if (minimalJ < 0) {
throw new RuntimeException("Internal error: failed to encode \"" + stringToEncode + "\"");
}
List<Integer> intsAL = new ArrayList<>();
InputEdge current = edges[inputLength][minimalJ];
while (current != null) {
if (current.isFNC1()) {
intsAL.add(0, 1000);
} else {
byte[] bytes = encoderSet.encode(current.c,current.encoderIndex);
for (int i = bytes.length - 1; i >= 0; i--) {
intsAL.add(0, (bytes[i] & 0xFF));
}
}
int previousEncoderIndex = current.previous == null ? 0 : current.previous.encoderIndex;
if (previousEncoderIndex != current.encoderIndex) {
intsAL.add(0,256 + encoderSet.getECIValue(current.encoderIndex));
}
current = current.previous;
}
int[] ints = new int[intsAL.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = intsAL.get(i);
}
return ints;
}
private static final class InputEdge {
private final char c;
private final int encoderIndex; //the encoding of this edge
private final InputEdge previous;
private final int cachedTotalSize;
private InputEdge(char c, ECIEncoderSet encoderSet, int encoderIndex, InputEdge previous, int fnc1) {
this.c = c == fnc1 ? 1000 : c;
this.encoderIndex = encoderIndex;
this.previous = previous;
int size = this.c == 1000 ? 1 : encoderSet.encode(c, encoderIndex).length;
int previousEncoderIndex = previous == null ? 0 : previous.encoderIndex;
if (previousEncoderIndex != encoderIndex) {
size += COST_PER_ECI;
}
if (previous != null) {
size += previous.cachedTotalSize;
}
this.cachedTotalSize = size;
}
boolean isFNC1() {
return c == 1000;
}
}
}

View file

@ -21,7 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import com.google.zxing.common.ECIEncoderSet;
import com.google.zxing.common.MinimalECIInput;
/**
* Encoder that encodes minimally
@ -1022,32 +1022,15 @@ public final class MinimalEncoder {
}
private static final class Input {
private static final class Input extends MinimalECIInput {
private static final int COST_PER_ECI = 3; // approximated (latch to ASCII + 2 codewords)
private final int[] bytes;
private final int fnc1;
private final SymbolShapeHint shape;
private final int macroId;
private Input(String stringToEncode, Charset priorityCharset, int fnc1, SymbolShapeHint shape, int macroId) {
this.fnc1 = fnc1;
super(stringToEncode, priorityCharset, fnc1);
this.shape = shape;
this.macroId = macroId;
ECIEncoderSet encoderSet = new ECIEncoderSet(stringToEncode, priorityCharset, fnc1);
if (encoderSet.length() == 1) { //optimization for the case when all can be encoded without ECI in ISO-8859-1
bytes = new int[stringToEncode.length()];
for (int i = 0; i < bytes.length; i++) {
char c = stringToEncode.charAt(i);
bytes[i] = c == fnc1 ? 1000 : (int) c;
}
} else {
bytes = encodeMinimally(stringToEncode, encoderSet, fnc1);
}
}
private int getFNC1Character() {
return fnc1;
}
private int getMacroId() {
@ -1057,154 +1040,5 @@ public final class MinimalEncoder {
private SymbolShapeHint getShapeHint() {
return shape;
}
private int length() {
return bytes.length;
}
boolean haveNCharacters(int index, int n) {
if (index + n - 1 >= bytes.length) {
return false;
}
for (int i = 0; i < n; i++) {
if (isECI(index + i)) {
return false;
}
}
return true;
}
private char charAt(int index) {
assert !isECI(index);
return isFNC1(index) ? (char) fnc1 : (char) bytes[index];
}
private boolean isECI(int index) {
return bytes[index] > 255 && bytes[index] <= 999;
}
private boolean isFNC1(int index) {
return bytes[index] == 1000;
}
private int getECIValue(int index) {
assert isECI(index);
return bytes[index] - 256;
}
static void addEdge(InputEdge[][] edges, int to, InputEdge edge) {
if (edges[to][edge.encoderIndex] == null ||
edges[to][edge.encoderIndex].cachedTotalSize > edge.cachedTotalSize) {
edges[to][edge.encoderIndex] = edge;
}
}
static void addEdges(String stringToEncode,
ECIEncoderSet encoderSet,
InputEdge[][] edges,
int from,
InputEdge previous,
int fnc1) {
char ch = stringToEncode.charAt(from);
int start = 0;
int end = encoderSet.length();
if (encoderSet.getPriorityEncoderIndex() >= 0 && (ch == fnc1 || encoderSet.canEncode(ch,
encoderSet.getPriorityEncoderIndex()))) {
start = encoderSet.getPriorityEncoderIndex();
end = start + 1;
}
for (int i = start; i < end; i++) {
if (ch == fnc1 || encoderSet.canEncode(ch,i)) {
addEdge(edges, from + 1, new InputEdge(ch, encoderSet, i, previous, fnc1));
}
}
}
static int[] encodeMinimally(String stringToEncode, ECIEncoderSet encoderSet, int fnc1) {
int inputLength = stringToEncode.length();
// Array that represents vertices. There is a vertex for every character and encoding.
InputEdge[][] edges = new InputEdge[inputLength + 1][encoderSet.length()];
addEdges(stringToEncode, encoderSet, edges, 0, null, fnc1);
for (int i = 1; i <= inputLength; i++) {
for (int j = 0; j < encoderSet.length(); j++) {
if (edges[i][j] != null && i < inputLength) {
addEdges(stringToEncode, encoderSet, edges, i, edges[i][j], fnc1);
}
}
//optimize memory by removing edges that have been passed.
for (int j = 0; j < encoderSet.length(); j++) {
edges[i - 1][j] = null;
}
}
int minimalJ = -1;
int minimalSize = Integer.MAX_VALUE;
for (int j = 0; j < encoderSet.length(); j++) {
if (edges[inputLength][j] != null) {
InputEdge edge = edges[inputLength][j];
if (edge.cachedTotalSize < minimalSize) {
minimalSize = edge.cachedTotalSize;
minimalJ = j;
}
}
}
if (minimalJ < 0) {
throw new RuntimeException("Internal error: failed to encode \"" + stringToEncode + "\"");
}
List<Integer> intsAL = new ArrayList<>();
InputEdge current = edges[inputLength][minimalJ];
while (current != null) {
if (current.isFNC1()) {
intsAL.add(0, 1000);
} else {
byte[] bytes = encoderSet.encode(current.c,current.encoderIndex);
for (int i = bytes.length - 1; i >= 0; i--) {
intsAL.add(0, (bytes[i] & 0xFF));
}
}
int previousEncoderIndex = current.previous == null ? 0 : current.previous.encoderIndex;
if (previousEncoderIndex != current.encoderIndex) {
intsAL.add(0,256 + encoderSet.getECIValue(current.encoderIndex));
}
current = current.previous;
}
int[] ints = new int[intsAL.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = intsAL.get(i);
}
return ints;
}
private static final class InputEdge {
private final char c;
private final int encoderIndex; //the encoding of this edge
private final InputEdge previous;
private final int cachedTotalSize;
private InputEdge(char c, ECIEncoderSet encoderSet, int encoderIndex, InputEdge previous, int fnc1) {
this.c = c == fnc1 ? 1000 : c;
this.encoderIndex = encoderIndex;
this.previous = previous;
int size = this.c == 1000 ? 1 : encoderSet.encode(c, encoderIndex).length;
int previousEncoderIndex = previous == null ? 0 : previous.encoderIndex;
if (previousEncoderIndex != encoderIndex) {
size += COST_PER_ECI;
}
if (previous != null) {
size += previous.cachedTotalSize;
}
this.cachedTotalSize = size;
}
boolean isFNC1() {
return c == 1000;
}
}
}
}

View file

@ -57,6 +57,7 @@ public final class PDF417Writer implements Writer {
PDF417 encoder = new PDF417();
int margin = WHITE_SPACE;
int errorCorrectionLevel = DEFAULT_ERROR_CORRECTION_LEVEL;
boolean autoECI = false;
if (hints != null) {
if (hints.containsKey(EncodeHintType.PDF417_COMPACT)) {
@ -82,9 +83,11 @@ public final class PDF417Writer implements Writer {
Charset encoding = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString());
encoder.setEncoding(encoding);
}
autoECI = hints.containsKey(EncodeHintType.PDF417_AUTO_ECI) &&
Boolean.parseBoolean(hints.get(EncodeHintType.PDF417_AUTO_ECI).toString());
}
return bitMatrixFromEncoder(encoder, contents, errorCorrectionLevel, width, height, margin);
return bitMatrixFromEncoder(encoder, contents, errorCorrectionLevel, width, height, margin, autoECI);
}
@Override
@ -103,8 +106,9 @@ public final class PDF417Writer implements Writer {
int errorCorrectionLevel,
int width,
int height,
int margin) throws WriterException {
encoder.generateBarcodeLogic(contents, errorCorrectionLevel);
int margin,
boolean autoECI) throws WriterException {
encoder.generateBarcodeLogic(contents, errorCorrectionLevel, autoECI);
int aspectRatio = 4;
byte[][] originalScale = encoder.getBarcodeMatrix().getScaledMatrix(1, aspectRatio);

View file

@ -104,7 +104,12 @@ final class DecodedBitStreamParser {
static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException {
StringBuilder result = new StringBuilder(codewords.length * 2);
Charset encoding = StandardCharsets.ISO_8859_1;
int codeIndex = textCompaction(codewords, 1, result);
int codeIndex = 1;
if (codewords[0] > 1 && codewords[codeIndex] == ECI_CHARSET) {
encoding = getECICharset(codewords[++codeIndex]);
codeIndex++;
}
codeIndex = textCompaction(codewords, codeIndex, result);
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
while (codeIndex < codewords[0]) {
int code = codewords[codeIndex++];
@ -123,12 +128,7 @@ final class DecodedBitStreamParser {
codeIndex = numericCompaction(codewords, codeIndex, result);
break;
case ECI_CHARSET:
CharacterSetECI charsetECI =
CharacterSetECI.getCharacterSetECIByValue(codewords[codeIndex++]);
if (charsetECI == null) {
throw FormatException.getFormatInstance();
}
encoding = charsetECI.getCharset();
encoding = getECICharset(codewords[codeIndex++]);
break;
case ECI_GENERAL_PURPOSE:
// Can't do anything with generic ECI; skip its 2 characters
@ -162,6 +162,15 @@ final class DecodedBitStreamParser {
return decoderResult;
}
private static Charset getECICharset(int eciValue) throws FormatException {
CharacterSetECI charsetECI =
CharacterSetECI.getCharacterSetECIByValue(eciValue);
if (charsetECI == null) {
throw FormatException.getFormatInstance();
}
return charsetECI.getCharset();
}
@SuppressWarnings("deprecation")
static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata)
throws FormatException {

View file

@ -643,10 +643,20 @@ public final class PDF417 {
* @throws WriterException if the contents cannot be encoded in this format
*/
public void generateBarcodeLogic(String msg, int errorCorrectionLevel) throws WriterException {
generateBarcodeLogic(msg, errorCorrectionLevel, false);
}
/**
* @param msg message to encode
* @param errorCorrectionLevel PDF417 error correction level to use
* @param autoECI automatically insert ECIs if needed
* @throws WriterException if the contents cannot be encoded in this format
*/
public void generateBarcodeLogic(String msg, int errorCorrectionLevel, boolean autoECI) throws WriterException {
//1. step: High-level encoding
int errorCorrectionCodeWords = PDF417ErrorCorrection.getErrorCorrectionCodewordCount(errorCorrectionLevel);
String highLevel = PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding);
String highLevel = PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding, autoECI);
int sourceCodeWords = highLevel.length();
int[] dimension = determineDimensions(sourceCodeWords, errorCorrectionCodeWords);

View file

@ -22,6 +22,8 @@ package com.google.zxing.pdf417.encoder;
import com.google.zxing.WriterException;
import com.google.zxing.common.CharacterSetECI;
import com.google.zxing.common.ECIInput;
import com.google.zxing.common.MinimalECIInput;
import java.math.BigInteger;
import java.nio.charset.Charset;
@ -159,71 +161,103 @@ final class PDF417HighLevelEncoder {
* @param compaction compaction mode to use
* @param encoding character encoding used to encode in default or byte compaction
* or {@code null} for default / not applicable
* @param autoECI encode input minimally using multiple ECIs if needed
* If autoECI encoding is specified and additionally {@code encoding} is specified, then the encoder
* will use the specified {@link Charset} for any character that can be encoded by it, regardless
* if a different encoding would lead to a more compact encoding. When no {@code encoding} is specified
* then charsets will be chosen so that the byte representation is minimal.
* @return the encoded message (the char values range from 0 to 928)
*/
static String encodeHighLevel(String msg, Compaction compaction, Charset encoding) throws WriterException {
static String encodeHighLevel(String msg, Compaction compaction, Charset encoding, boolean autoECI)
throws WriterException {
//the codewords 0..928 are encoded as Unicode characters
StringBuilder sb = new StringBuilder(msg.length());
if (encoding == null) {
encoding = DEFAULT_ENCODING;
} else if (!DEFAULT_ENCODING.equals(encoding)) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(encoding);
if (eci != null) {
encodingECI(eci.getValue(), sb);
ECIInput input;
if (autoECI) {
input = new MinimalECIInput(msg, encoding, -1);
} else {
input = new NoECIInput(msg);
if (encoding == null) {
encoding = DEFAULT_ENCODING;
} else if (!DEFAULT_ENCODING.equals(encoding)) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(encoding);
if (eci != null) {
encodingECI(eci.getValue(), sb);
}
}
}
int len = msg.length();
int len = input.length();
int p = 0;
int textSubMode = SUBMODE_ALPHA;
// User selected encoding mode
switch (compaction) {
case TEXT:
encodeText(msg, p, len, sb, textSubMode);
encodeText(input, p, len, sb, textSubMode);
break;
case BYTE:
byte[] msgBytes = msg.getBytes(encoding);
encodeBinary(msgBytes, p, msgBytes.length, BYTE_COMPACTION, sb);
if (autoECI) {
encodeMultiECIBinary(input, 0, input.length(), TEXT_COMPACTION, sb);
} else {
byte[] msgBytes = input.toString().getBytes(encoding);
encodeBinary(msgBytes, p, msgBytes.length, BYTE_COMPACTION, sb);
}
break;
case NUMERIC:
sb.append((char) LATCH_TO_NUMERIC);
encodeNumeric(msg, p, len, sb);
encodeNumeric(input, p, len, sb);
break;
default:
int encodingMode = TEXT_COMPACTION; //Default mode, see 4.4.2.1
while (p < len) {
int n = determineConsecutiveDigitCount(msg, p);
while (p < len && input.isECI(p)) {
encodingECI(input.getECIValue(p), sb);
p++;
}
if (p >= len) {
break;
}
int n = determineConsecutiveDigitCount(input, p);
if (n >= 13) {
sb.append((char) LATCH_TO_NUMERIC);
encodingMode = NUMERIC_COMPACTION;
textSubMode = SUBMODE_ALPHA; //Reset after latch
encodeNumeric(msg, p, n, sb);
encodeNumeric(input, p, n, sb);
p += n;
} else {
int t = determineConsecutiveTextCount(msg, p);
int t = determineConsecutiveTextCount(input, p);
if (t >= 5 || n == len) {
if (encodingMode != TEXT_COMPACTION) {
sb.append((char) LATCH_TO_TEXT);
encodingMode = TEXT_COMPACTION;
textSubMode = SUBMODE_ALPHA; //start with submode alpha after latch
}
textSubMode = encodeText(msg, p, t, sb, textSubMode);
textSubMode = encodeText(input, p, t, sb, textSubMode);
p += t;
} else {
int b = determineConsecutiveBinaryCount(msg, p, encoding);
int b = determineConsecutiveBinaryCount(input, p, autoECI ? null : encoding);
if (b == 0) {
b = 1;
}
byte[] bytes = msg.substring(p, p + b).getBytes(encoding);
if (bytes.length == 1 && encodingMode == TEXT_COMPACTION) {
byte[] bytes = autoECI ? null : input.subSequence(p, p + b).toString().getBytes(encoding);
if (((bytes == null && b == 1) || (bytes != null && bytes.length == 1))
&& encodingMode == TEXT_COMPACTION) {
//Switch for one byte (instead of latch)
encodeBinary(bytes, 0, 1, TEXT_COMPACTION, sb);
if (autoECI) {
encodeMultiECIBinary(input, p, 1, TEXT_COMPACTION, sb);
} else {
encodeBinary(bytes, 0, 1, TEXT_COMPACTION, sb);
}
} else {
//Mode latch performed by encodeBinary()
encodeBinary(bytes, 0, bytes.length, encodingMode, sb);
if (autoECI) {
encodeMultiECIBinary(input, p, p + b, encodingMode, sb);
} else {
encodeBinary(bytes, 0, bytes.length, encodingMode, sb);
}
encodingMode = BYTE_COMPACTION;
textSubMode = SUBMODE_ALPHA; //Reset after latch
}
@ -241,109 +275,113 @@ final class PDF417HighLevelEncoder {
* Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E),
* chapter 4.4.2.
*
* @param msg the message
* @param input the input
* @param startpos the start position within the message
* @param count the number of characters to encode
* @param sb receives the encoded codewords
* @param initialSubmode should normally be SUBMODE_ALPHA
* @return the text submode in which this method ends
*/
private static int encodeText(CharSequence msg,
private static int encodeText(ECIInput input,
int startpos,
int count,
StringBuilder sb,
int initialSubmode) {
int initialSubmode) throws WriterException {
StringBuilder tmp = new StringBuilder(count);
int submode = initialSubmode;
int idx = 0;
while (true) {
char ch = msg.charAt(startpos + idx);
switch (submode) {
case SUBMODE_ALPHA:
if (isAlphaUpper(ch)) {
if (ch == ' ') {
tmp.append((char) 26); //space
} else {
tmp.append((char) (ch - 65));
}
} else {
if (isAlphaLower(ch)) {
submode = SUBMODE_LOWER;
tmp.append((char) 27); //ll
continue;
} else if (isMixed(ch)) {
submode = SUBMODE_MIXED;
tmp.append((char) 28); //ml
continue;
} else {
tmp.append((char) 29); //ps
tmp.append((char) PUNCTUATION[ch]);
break;
}
}
break;
case SUBMODE_LOWER:
if (isAlphaLower(ch)) {
if (ch == ' ') {
tmp.append((char) 26); //space
} else {
tmp.append((char) (ch - 97));
}
} else {
if (input.isECI(startpos + idx)) {
encodingECI(input.getECIValue(startpos + idx), sb);
idx++;
} else {
char ch = input.charAt(startpos + idx);
switch (submode) {
case SUBMODE_ALPHA:
if (isAlphaUpper(ch)) {
tmp.append((char) 27); //as
tmp.append((char) (ch - 65));
//space cannot happen here, it is also in "Lower"
break;
} else if (isMixed(ch)) {
submode = SUBMODE_MIXED;
tmp.append((char) 28); //ml
continue;
} else {
tmp.append((char) 29); //ps
tmp.append((char) PUNCTUATION[ch]);
break;
}
}
break;
case SUBMODE_MIXED:
if (isMixed(ch)) {
tmp.append((char) MIXED[ch]);
} else {
if (isAlphaUpper(ch)) {
submode = SUBMODE_ALPHA;
tmp.append((char) 28); //al
continue;
} else if (isAlphaLower(ch)) {
submode = SUBMODE_LOWER;
tmp.append((char) 27); //ll
continue;
} else {
if (startpos + idx + 1 < count) {
char next = msg.charAt(startpos + idx + 1);
if (isPunctuation(next)) {
submode = SUBMODE_PUNCTUATION;
tmp.append((char) 25); //pl
continue;
}
if (ch == ' ') {
tmp.append((char) 26); //space
} else {
tmp.append((char) (ch - 65));
}
} else {
if (isAlphaLower(ch)) {
submode = SUBMODE_LOWER;
tmp.append((char) 27); //ll
continue;
} else if (isMixed(ch)) {
submode = SUBMODE_MIXED;
tmp.append((char) 28); //ml
continue;
} else {
tmp.append((char) 29); //ps
tmp.append((char) PUNCTUATION[ch]);
break;
}
tmp.append((char) 29); //ps
tmp.append((char) PUNCTUATION[ch]);
}
}
break;
case SUBMODE_LOWER:
if (isAlphaLower(ch)) {
if (ch == ' ') {
tmp.append((char) 26); //space
} else {
tmp.append((char) (ch - 97));
}
} else {
if (isAlphaUpper(ch)) {
tmp.append((char) 27); //as
tmp.append((char) (ch - 65));
//space cannot happen here, it is also in "Lower"
break;
} else if (isMixed(ch)) {
submode = SUBMODE_MIXED;
tmp.append((char) 28); //ml
continue;
} else {
tmp.append((char) 29); //ps
tmp.append((char) PUNCTUATION[ch]);
break;
}
}
break;
case SUBMODE_MIXED:
if (isMixed(ch)) {
tmp.append((char) MIXED[ch]);
} else {
if (isAlphaUpper(ch)) {
submode = SUBMODE_ALPHA;
tmp.append((char) 28); //al
continue;
} else if (isAlphaLower(ch)) {
submode = SUBMODE_LOWER;
tmp.append((char) 27); //ll
continue;
} else {
if (startpos + idx + 1 < count) {
if (!input.isECI(startpos + idx + 1) && isPunctuation(input.charAt(startpos + idx + 1))) {
submode = SUBMODE_PUNCTUATION;
tmp.append((char) 25); //pl
continue;
}
}
tmp.append((char) 29); //ps
tmp.append((char) PUNCTUATION[ch]);
}
}
break;
default: //SUBMODE_PUNCTUATION
if (isPunctuation(ch)) {
tmp.append((char) PUNCTUATION[ch]);
} else {
submode = SUBMODE_ALPHA;
tmp.append((char) 29); //al
continue;
}
}
idx++;
if (idx >= count) {
break;
default: //SUBMODE_PUNCTUATION
if (isPunctuation(ch)) {
tmp.append((char) PUNCTUATION[ch]);
} else {
submode = SUBMODE_ALPHA;
tmp.append((char) 29); //al
continue;
}
}
idx++;
if (idx >= count) {
break;
}
}
}
char h = 0;
@ -363,6 +401,56 @@ final class PDF417HighLevelEncoder {
return submode;
}
/**
* Encode all of the message using Byte Compaction as described in ISO/IEC 15438:2001(E)
*
* @param input the input
* @param startpos the start position within the message
* @param count the number of bytes to encode
* @param startmode the mode from which this method starts
* @param sb receives the encoded codewords
*/
private static void encodeMultiECIBinary(ECIInput input,
int startpos,
int count,
int startmode,
StringBuilder sb) throws WriterException {
final int end = Math.min(startpos + count, input.length());
int localStart = startpos;
while (true) {
//encode all leading ECIs and advance localStart
while (localStart < end && input.isECI(localStart)) {
encodingECI(input.getECIValue(localStart), sb);
localStart++;
}
int localEnd = localStart;
//advance end until before the next ECI
while (localEnd < end && !input.isECI(localEnd)) {
localEnd++;
}
final int localCount = localEnd - localStart;
if (localCount <= 0) {
//done
break;
} else {
//encode the segment
encodeBinary(subBytes(input, localStart, localEnd),
0, localCount, localStart == startpos ? startmode : BYTE_COMPACTION, sb);
localStart = localEnd + 1;
}
}
}
static byte[] subBytes(ECIInput input, int start, int end) {
final int count = end - start;
byte[] result = new byte[count];
for (int i = start; i < end; i++) {
result[i - start] = (byte) (input.charAt(i) & 0xff);
}
return result;
}
/**
* Encode parts of the message using Byte Compaction as described in ISO/IEC 15438:2001(E),
* chapter 4.4.3. The Unicode characters will be converted to binary using the cp437
@ -416,7 +504,7 @@ final class PDF417HighLevelEncoder {
}
}
private static void encodeNumeric(String msg, int startpos, int count, StringBuilder sb) {
private static void encodeNumeric(ECIInput input, int startpos, int count, StringBuilder sb) {
int idx = 0;
StringBuilder tmp = new StringBuilder(count / 3 + 1);
BigInteger num900 = BigInteger.valueOf(900);
@ -424,7 +512,7 @@ final class PDF417HighLevelEncoder {
while (idx < count) {
tmp.setLength(0);
int len = Math.min(44, count - idx);
String part = '1' + msg.substring(startpos + idx, startpos + idx + len);
String part = "1" + input.subSequence(startpos + idx, startpos + idx + len);
BigInteger bigint = new BigInteger(part);
do {
tmp.append((char) bigint.mod(num900).intValue());
@ -467,22 +555,18 @@ final class PDF417HighLevelEncoder {
/**
* Determines the number of consecutive characters that are encodable using numeric compaction.
*
* @param msg the message
* @param startpos the start position within the message
* @param input the input
* @param startpos the start position within the input
* @return the requested character count
*/
private static int determineConsecutiveDigitCount(CharSequence msg, int startpos) {
private static int determineConsecutiveDigitCount(ECIInput input, int startpos) {
int count = 0;
int len = msg.length();
final int len = input.length();
int idx = startpos;
if (idx < len) {
char ch = msg.charAt(idx);
while (isDigit(ch) && idx < len) {
while (idx < len && !input.isECI(idx) && isDigit(input.charAt(idx))) {
count++;
idx++;
if (idx < len) {
ch = msg.charAt(idx);
}
}
}
return count;
@ -491,22 +575,18 @@ final class PDF417HighLevelEncoder {
/**
* Determines the number of consecutive characters that are encodable using text compaction.
*
* @param msg the message
* @param startpos the start position within the message
* @param input the input
* @param startpos the start position within the input
* @return the requested character count
*/
private static int determineConsecutiveTextCount(CharSequence msg, int startpos) {
int len = msg.length();
private static int determineConsecutiveTextCount(ECIInput input, int startpos) {
final int len = input.length();
int idx = startpos;
while (idx < len) {
char ch = msg.charAt(idx);
int numericCount = 0;
while (numericCount < 13 && isDigit(ch) && idx < len) {
while (numericCount < 13 && idx < len && !input.isECI(idx) && isDigit(input.charAt(idx))) {
numericCount++;
idx++;
if (idx < len) {
ch = msg.charAt(idx);
}
}
if (numericCount >= 13) {
return idx - startpos - numericCount;
@ -515,10 +595,9 @@ final class PDF417HighLevelEncoder {
//Heuristic: All text-encodable chars or digits are binary encodable
continue;
}
ch = msg.charAt(idx);
//Check if character is encodable
if (!isText(ch)) {
if (input.isECI(idx) || !isText(input.charAt(idx))) {
break;
}
idx++;
@ -529,35 +608,35 @@ final class PDF417HighLevelEncoder {
/**
* Determines the number of consecutive characters that are encodable using binary compaction.
*
* @param msg the message
* @param input the input
* @param startpos the start position within the message
* @param encoding the charset used to convert the message to a byte array
* @return the requested character count
*/
private static int determineConsecutiveBinaryCount(String msg, int startpos, Charset encoding)
private static int determineConsecutiveBinaryCount(ECIInput input, int startpos, Charset encoding)
throws WriterException {
CharsetEncoder encoder = encoding.newEncoder();
int len = msg.length();
CharsetEncoder encoder = encoding == null ? null : encoding.newEncoder();
int len = input.length();
int idx = startpos;
while (idx < len) {
char ch = msg.charAt(idx);
int numericCount = 0;
while (numericCount < 13 && isDigit(ch)) {
int i = idx;
while (numericCount < 13 && !input.isECI(i) && isDigit(input.charAt(i))) {
numericCount++;
//textCount++;
int i = idx + numericCount;
i = idx + numericCount;
if (i >= len) {
break;
}
ch = msg.charAt(i);
}
if (numericCount >= 13) {
return idx - startpos;
}
ch = msg.charAt(idx);
if (!encoder.canEncode(ch)) {
if (encoder != null && !encoder.canEncode(input.charAt(idx))) {
assert input instanceof NoECIInput;
char ch = input.charAt(idx);
throw new WriterException("Non-encodable character detected: " + ch + " (Unicode: " + (int) ch + ')');
}
idx++;
@ -581,4 +660,40 @@ final class PDF417HighLevelEncoder {
}
}
private static final class NoECIInput implements ECIInput {
String input;
private NoECIInput(String input) {
this.input = input;
}
public int length() {
return input.length();
}
public char charAt(int index) {
return input.charAt(index);
}
public boolean isECI(int index) {
return false;
}
public int getECIValue(int index) {
return -1;
}
public boolean haveNCharacters(int index, int n) {
return index + n <= input.length();
}
public CharSequence subSequence(int start, int end) {
return input.subSequence(start, end);
}
public String toString() {
return input;
}
}
}

View file

@ -29,7 +29,10 @@ public final class PDF417HighLevelEncoderTestAdapter {
private PDF417HighLevelEncoderTestAdapter() {
}
public static String encodeHighLevel(String msg, Compaction compaction, Charset encoding) throws WriterException {
return PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding);
public static String encodeHighLevel(String msg,
Compaction compaction,
Charset encoding,
boolean autoECI) throws WriterException {
return PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding, autoECI);
}
}

View file

@ -25,6 +25,7 @@ import com.google.zxing.pdf417.encoder.PDF417HighLevelEncoderTestAdapter;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Random;
@ -283,21 +284,76 @@ public class PDF417DecoderTestCase extends Assert {
assertEquals(4190044, total);
}
@Test
public void testECIEnglishHiragana() throws Exception {
//multi ECI UTF-8, UTF-16 and ISO-8859-1
performECITest(new char[] {'a', '1', '\u3040'}, new float[] {20f, 1f, 10f}, 102583, 110914);
}
@Test
public void testECIEnglishKatakana() throws Exception {
//multi ECI UTF-8, UTF-16 and ISO-8859-1
performECITest(new char[] {'a', '1', '\u30a0'}, new float[] {20f, 1f, 10f}, 104691, 110914);
}
@Test
public void testECIEnglishHalfWidthKatakana() throws Exception {
//single ECI
performECITest(new char[] {'a', '1', '\uff80'}, new float[] {20f, 1f, 10f}, 80463, 110914);
}
@Test
public void testECIEnglishChinese() throws Exception {
//single ECI
performECITest(new char[] {'a', '1', '\u4e00'}, new float[] {20f, 1f, 10f}, 95643, 110914);
}
@Test
public void testECIGermanCyrillic() throws Exception {
//single ECI since the German Umlaut is in ISO-8859-1
performECITest(new char[] {'a', '1', '\u00c4', '\u042f'}, new float[] {20f, 1f, 1f, 10f}, 80529, 96007);
}
@Test
public void testECIEnglishCzechCyrillic1() throws Exception {
//multi ECI between ISO-8859-2 and ISO-8859-5
performECITest(new char[] {'a', '1', '\u010c', '\u042f'}, new float[] {10f, 1f, 10f, 10f}, 91482, 124525);
}
@Test
public void testECIEnglishCzechCyrillic2() throws Exception {
//multi ECI between ISO-8859-2 and ISO-8859-5
performECITest(new char[] {'a', '1', '\u010c', '\u042f'}, new float[] {40f, 1f, 10f, 10f}, 79331, 88236);
}
@Test
public void testECIEnglishArabicCyrillic() throws Exception {
//multi ECI between UTF-8 (ISO-8859-6 is excluded in CharacterSetECI) and ISO-8859-5
performECITest(new char[] {'a', '1', '\u0620', '\u042f'}, new float[] {10f, 1f, 10f, 10f}, 111508, 124525);
}
private static void encodeDecode(String input, int expectedLength) throws WriterException, FormatException {
assertEquals(expectedLength, encodeDecode(input));
}
private static int encodeDecode(String input) throws WriterException, FormatException {
String s = PDF417HighLevelEncoderTestAdapter.encodeHighLevel(input, Compaction.AUTO, null);
int[] codewords = new int[s.length() + 1];
codewords[0] = codewords.length;
for (int i = 1; i < codewords.length; i++) {
codewords[i] = s.charAt(i - 1);
}
DecoderResult result = DecodedBitStreamParser.decode(codewords, "0");
return encodeDecode(input, null, false, true);
}
assertEquals(input, result.getText());
return codewords.length;
private static int encodeDecode(String input, Charset charset, boolean autoECI, boolean decode)
throws WriterException, FormatException {
String s = PDF417HighLevelEncoderTestAdapter.encodeHighLevel(input, Compaction.AUTO, charset, autoECI);
if (decode) {
int[] codewords = new int[s.length() + 1];
codewords[0] = codewords.length;
for (int i = 1; i < codewords.length; i++) {
codewords[i] = s.charAt(i - 1);
}
DecoderResult result = DecodedBitStreamParser.decode(codewords, "0");
assertEquals(input, result.getText());
}
return s.length() + 1;
}
private static int getEndIndex(int length, char[] chars) {
@ -338,4 +394,61 @@ public class PDF417DecoderTestCase extends Assert {
}
}
private static void performECITest(char[] chars,
float[] weights,
int expectedMinLength,
int expectedUTFLength) throws WriterException, FormatException {
Random random = new Random(0);
int minLength = 0;
int utfLength = 0;
for (int i = 0; i < 1000; i++) {
String s = generateText(random, 100, chars, weights);
minLength += encodeDecode(s, null, true, false);
// TODO: Use this instead when the decoder supports multi ECI input
//minLength += encodeDecode(s, null, true, true);
utfLength += encodeDecode(s, StandardCharsets.UTF_8, false, true);
}
assertEquals(expectedMinLength, minLength);
assertEquals(expectedUTFLength, utfLength);
}
private static String generateText(Random random, int maxWidth, char[] chars, float[] weights) {
StringBuilder result = new StringBuilder();
final int maxWordWidth = 7;
float total = 0;
for (int i = 0; i < weights.length; i++) {
total += weights[i];
}
for (int i = 0; i < weights.length; i++) {
weights[i] /= total;
}
int cnt = 0;
do {
float maxValue = 0;
int maxIndex = 0;
for (int j = 0; j < weights.length; j++) {
float value = random.nextFloat() * weights[j];
if (value > maxValue) {
maxValue = value;
maxIndex = j;
}
}
final float wordLength = maxWordWidth * random.nextFloat();
if (wordLength > 0 && result.length() > 0) {
result.append(' ');
}
for (int j = 0; j < wordLength; j++) {
char c = chars[maxIndex];
if (j == 0 && c >= 'a' && c <= 'z' && random.nextBoolean()) {
c = (char) (c - 'a' + 'A');
}
result.append(c);
}
if (cnt % 2 != 0 && random.nextBoolean()) {
result.append('.');
}
cnt++;
} while (result.length() < maxWidth - maxWordWidth);
return result.toString();
}
}

View file

@ -29,7 +29,7 @@ public final class PDF417EncoderTestCase extends Assert {
@Test
public void testEncodeAuto() throws Exception {
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
"ABCD", Compaction.AUTO, StandardCharsets.UTF_8);
"ABCD", Compaction.AUTO, StandardCharsets.UTF_8, false);
assertEquals("\u039f\u001A\u0385ABCD", encoded);
}
@ -37,33 +37,33 @@ public final class PDF417EncoderTestCase extends Assert {
public void testEncodeAutoWithSpecialChars() throws Exception {
// Just check if this does not throw an exception
PDF417HighLevelEncoder.encodeHighLevel(
"1%§s ?aG$", Compaction.AUTO, StandardCharsets.UTF_8);
"1%§s ?aG$", Compaction.AUTO, StandardCharsets.UTF_8, false);
}
@Test
public void testEncodeIso88591WithSpecialChars() throws Exception {
// Just check if this does not throw an exception
PDF417HighLevelEncoder.encodeHighLevel("asdfg§asd", Compaction.AUTO, StandardCharsets.ISO_8859_1);
PDF417HighLevelEncoder.encodeHighLevel("asdfg§asd", Compaction.AUTO, StandardCharsets.ISO_8859_1, false);
}
@Test
public void testEncodeText() throws Exception {
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
"ABCD", Compaction.TEXT, StandardCharsets.UTF_8);
"ABCD", Compaction.TEXT, StandardCharsets.UTF_8, false);
assertEquals("Ο\u001A\u0001?", encoded);
}
@Test
public void testEncodeNumeric() throws Exception {
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
"1234", Compaction.NUMERIC, StandardCharsets.UTF_8);
"1234", Compaction.NUMERIC, StandardCharsets.UTF_8, false);
assertEquals("\u039f\u001A\u0386\f\u01b2", encoded);
}
@Test
public void testEncodeByte() throws Exception {
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
"abcd", Compaction.BYTE, StandardCharsets.UTF_8);
"abcd", Compaction.BYTE, StandardCharsets.UTF_8, false);
assertEquals("\u039f\u001A\u0385abcd", encoded);
}