mirror of
https://github.com/zxing/zxing.git
synced 2025-03-05 20:48:51 -08:00
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:
parent
4bd257e8c5
commit
92854d4a55
|
@ -103,6 +103,16 @@ public enum EncodeHintType {
|
||||||
*/
|
*/
|
||||||
PDF417_DIMENSIONS,
|
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.
|
* Specifies the required number of layers for an Aztec code.
|
||||||
* A negative number (-1, -2, -3, -4) specifies a compact Aztec code.
|
* A negative number (-1, -2, -3, -4) specifies a compact Aztec code.
|
||||||
|
|
107
core/src/main/java/com/google/zxing/common/ECIInput.java
Executable file
107
core/src/main/java/com/google/zxing/common/ECIInput.java
Executable 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);
|
||||||
|
}
|
325
core/src/main/java/com/google/zxing/common/MinimalECIInput.java
Executable file
325
core/src/main/java/com/google/zxing/common/MinimalECIInput.java
Executable 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.zxing.common.ECIEncoderSet;
|
import com.google.zxing.common.MinimalECIInput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encoder that encodes minimally
|
* 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 SymbolShapeHint shape;
|
||||||
private final int macroId;
|
private final int macroId;
|
||||||
|
|
||||||
private Input(String stringToEncode, Charset priorityCharset, int fnc1, SymbolShapeHint shape, 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.shape = shape;
|
||||||
this.macroId = macroId;
|
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() {
|
private int getMacroId() {
|
||||||
|
@ -1057,154 +1040,5 @@ public final class MinimalEncoder {
|
||||||
private SymbolShapeHint getShapeHint() {
|
private SymbolShapeHint getShapeHint() {
|
||||||
return shape;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ public final class PDF417Writer implements Writer {
|
||||||
PDF417 encoder = new PDF417();
|
PDF417 encoder = new PDF417();
|
||||||
int margin = WHITE_SPACE;
|
int margin = WHITE_SPACE;
|
||||||
int errorCorrectionLevel = DEFAULT_ERROR_CORRECTION_LEVEL;
|
int errorCorrectionLevel = DEFAULT_ERROR_CORRECTION_LEVEL;
|
||||||
|
boolean autoECI = false;
|
||||||
|
|
||||||
if (hints != null) {
|
if (hints != null) {
|
||||||
if (hints.containsKey(EncodeHintType.PDF417_COMPACT)) {
|
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());
|
Charset encoding = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString());
|
||||||
encoder.setEncoding(encoding);
|
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
|
@Override
|
||||||
|
@ -103,8 +106,9 @@ public final class PDF417Writer implements Writer {
|
||||||
int errorCorrectionLevel,
|
int errorCorrectionLevel,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
int margin) throws WriterException {
|
int margin,
|
||||||
encoder.generateBarcodeLogic(contents, errorCorrectionLevel);
|
boolean autoECI) throws WriterException {
|
||||||
|
encoder.generateBarcodeLogic(contents, errorCorrectionLevel, autoECI);
|
||||||
|
|
||||||
int aspectRatio = 4;
|
int aspectRatio = 4;
|
||||||
byte[][] originalScale = encoder.getBarcodeMatrix().getScaledMatrix(1, aspectRatio);
|
byte[][] originalScale = encoder.getBarcodeMatrix().getScaledMatrix(1, aspectRatio);
|
||||||
|
|
|
@ -104,7 +104,12 @@ final class DecodedBitStreamParser {
|
||||||
static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException {
|
static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException {
|
||||||
StringBuilder result = new StringBuilder(codewords.length * 2);
|
StringBuilder result = new StringBuilder(codewords.length * 2);
|
||||||
Charset encoding = StandardCharsets.ISO_8859_1;
|
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();
|
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
|
||||||
while (codeIndex < codewords[0]) {
|
while (codeIndex < codewords[0]) {
|
||||||
int code = codewords[codeIndex++];
|
int code = codewords[codeIndex++];
|
||||||
|
@ -123,12 +128,7 @@ final class DecodedBitStreamParser {
|
||||||
codeIndex = numericCompaction(codewords, codeIndex, result);
|
codeIndex = numericCompaction(codewords, codeIndex, result);
|
||||||
break;
|
break;
|
||||||
case ECI_CHARSET:
|
case ECI_CHARSET:
|
||||||
CharacterSetECI charsetECI =
|
encoding = getECICharset(codewords[codeIndex++]);
|
||||||
CharacterSetECI.getCharacterSetECIByValue(codewords[codeIndex++]);
|
|
||||||
if (charsetECI == null) {
|
|
||||||
throw FormatException.getFormatInstance();
|
|
||||||
}
|
|
||||||
encoding = charsetECI.getCharset();
|
|
||||||
break;
|
break;
|
||||||
case ECI_GENERAL_PURPOSE:
|
case ECI_GENERAL_PURPOSE:
|
||||||
// Can't do anything with generic ECI; skip its 2 characters
|
// Can't do anything with generic ECI; skip its 2 characters
|
||||||
|
@ -162,6 +162,15 @@ final class DecodedBitStreamParser {
|
||||||
return decoderResult;
|
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")
|
@SuppressWarnings("deprecation")
|
||||||
static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata)
|
static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
|
|
|
@ -643,10 +643,20 @@ public final class PDF417 {
|
||||||
* @throws WriterException if the contents cannot be encoded in this format
|
* @throws WriterException if the contents cannot be encoded in this format
|
||||||
*/
|
*/
|
||||||
public void generateBarcodeLogic(String msg, int errorCorrectionLevel) throws WriterException {
|
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
|
//1. step: High-level encoding
|
||||||
int errorCorrectionCodeWords = PDF417ErrorCorrection.getErrorCorrectionCodewordCount(errorCorrectionLevel);
|
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 sourceCodeWords = highLevel.length();
|
||||||
|
|
||||||
int[] dimension = determineDimensions(sourceCodeWords, errorCorrectionCodeWords);
|
int[] dimension = determineDimensions(sourceCodeWords, errorCorrectionCodeWords);
|
||||||
|
|
|
@ -22,6 +22,8 @@ package com.google.zxing.pdf417.encoder;
|
||||||
|
|
||||||
import com.google.zxing.WriterException;
|
import com.google.zxing.WriterException;
|
||||||
import com.google.zxing.common.CharacterSetECI;
|
import com.google.zxing.common.CharacterSetECI;
|
||||||
|
import com.google.zxing.common.ECIInput;
|
||||||
|
import com.google.zxing.common.MinimalECIInput;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -159,71 +161,103 @@ final class PDF417HighLevelEncoder {
|
||||||
* @param compaction compaction mode to use
|
* @param compaction compaction mode to use
|
||||||
* @param encoding character encoding used to encode in default or byte compaction
|
* @param encoding character encoding used to encode in default or byte compaction
|
||||||
* or {@code null} for default / not applicable
|
* 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)
|
* @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
|
//the codewords 0..928 are encoded as Unicode characters
|
||||||
StringBuilder sb = new StringBuilder(msg.length());
|
StringBuilder sb = new StringBuilder(msg.length());
|
||||||
|
|
||||||
if (encoding == null) {
|
ECIInput input;
|
||||||
encoding = DEFAULT_ENCODING;
|
if (autoECI) {
|
||||||
} else if (!DEFAULT_ENCODING.equals(encoding)) {
|
input = new MinimalECIInput(msg, encoding, -1);
|
||||||
CharacterSetECI eci = CharacterSetECI.getCharacterSetECI(encoding);
|
} else {
|
||||||
if (eci != null) {
|
input = new NoECIInput(msg);
|
||||||
encodingECI(eci.getValue(), sb);
|
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 p = 0;
|
||||||
int textSubMode = SUBMODE_ALPHA;
|
int textSubMode = SUBMODE_ALPHA;
|
||||||
|
|
||||||
// User selected encoding mode
|
// User selected encoding mode
|
||||||
switch (compaction) {
|
switch (compaction) {
|
||||||
case TEXT:
|
case TEXT:
|
||||||
encodeText(msg, p, len, sb, textSubMode);
|
encodeText(input, p, len, sb, textSubMode);
|
||||||
break;
|
break;
|
||||||
case BYTE:
|
case BYTE:
|
||||||
byte[] msgBytes = msg.getBytes(encoding);
|
if (autoECI) {
|
||||||
encodeBinary(msgBytes, p, msgBytes.length, BYTE_COMPACTION, sb);
|
encodeMultiECIBinary(input, 0, input.length(), TEXT_COMPACTION, sb);
|
||||||
|
} else {
|
||||||
|
byte[] msgBytes = input.toString().getBytes(encoding);
|
||||||
|
encodeBinary(msgBytes, p, msgBytes.length, BYTE_COMPACTION, sb);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case NUMERIC:
|
case NUMERIC:
|
||||||
sb.append((char) LATCH_TO_NUMERIC);
|
sb.append((char) LATCH_TO_NUMERIC);
|
||||||
encodeNumeric(msg, p, len, sb);
|
encodeNumeric(input, p, len, sb);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
int encodingMode = TEXT_COMPACTION; //Default mode, see 4.4.2.1
|
int encodingMode = TEXT_COMPACTION; //Default mode, see 4.4.2.1
|
||||||
while (p < len) {
|
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) {
|
if (n >= 13) {
|
||||||
sb.append((char) LATCH_TO_NUMERIC);
|
sb.append((char) LATCH_TO_NUMERIC);
|
||||||
encodingMode = NUMERIC_COMPACTION;
|
encodingMode = NUMERIC_COMPACTION;
|
||||||
textSubMode = SUBMODE_ALPHA; //Reset after latch
|
textSubMode = SUBMODE_ALPHA; //Reset after latch
|
||||||
encodeNumeric(msg, p, n, sb);
|
encodeNumeric(input, p, n, sb);
|
||||||
p += n;
|
p += n;
|
||||||
} else {
|
} else {
|
||||||
int t = determineConsecutiveTextCount(msg, p);
|
int t = determineConsecutiveTextCount(input, p);
|
||||||
if (t >= 5 || n == len) {
|
if (t >= 5 || n == len) {
|
||||||
if (encodingMode != TEXT_COMPACTION) {
|
if (encodingMode != TEXT_COMPACTION) {
|
||||||
sb.append((char) LATCH_TO_TEXT);
|
sb.append((char) LATCH_TO_TEXT);
|
||||||
encodingMode = TEXT_COMPACTION;
|
encodingMode = TEXT_COMPACTION;
|
||||||
textSubMode = SUBMODE_ALPHA; //start with submode alpha after latch
|
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;
|
p += t;
|
||||||
} else {
|
} else {
|
||||||
int b = determineConsecutiveBinaryCount(msg, p, encoding);
|
int b = determineConsecutiveBinaryCount(input, p, autoECI ? null : encoding);
|
||||||
if (b == 0) {
|
if (b == 0) {
|
||||||
b = 1;
|
b = 1;
|
||||||
}
|
}
|
||||||
byte[] bytes = msg.substring(p, p + b).getBytes(encoding);
|
byte[] bytes = autoECI ? null : input.subSequence(p, p + b).toString().getBytes(encoding);
|
||||||
if (bytes.length == 1 && encodingMode == TEXT_COMPACTION) {
|
if (((bytes == null && b == 1) || (bytes != null && bytes.length == 1))
|
||||||
|
&& encodingMode == TEXT_COMPACTION) {
|
||||||
//Switch for one byte (instead of latch)
|
//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 {
|
} else {
|
||||||
//Mode latch performed by encodeBinary()
|
//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;
|
encodingMode = BYTE_COMPACTION;
|
||||||
textSubMode = SUBMODE_ALPHA; //Reset after latch
|
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),
|
* Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E),
|
||||||
* chapter 4.4.2.
|
* chapter 4.4.2.
|
||||||
*
|
*
|
||||||
* @param msg the message
|
* @param input the input
|
||||||
* @param startpos the start position within the message
|
* @param startpos the start position within the message
|
||||||
* @param count the number of characters to encode
|
* @param count the number of characters to encode
|
||||||
* @param sb receives the encoded codewords
|
* @param sb receives the encoded codewords
|
||||||
* @param initialSubmode should normally be SUBMODE_ALPHA
|
* @param initialSubmode should normally be SUBMODE_ALPHA
|
||||||
* @return the text submode in which this method ends
|
* @return the text submode in which this method ends
|
||||||
*/
|
*/
|
||||||
private static int encodeText(CharSequence msg,
|
private static int encodeText(ECIInput input,
|
||||||
int startpos,
|
int startpos,
|
||||||
int count,
|
int count,
|
||||||
StringBuilder sb,
|
StringBuilder sb,
|
||||||
int initialSubmode) {
|
int initialSubmode) throws WriterException {
|
||||||
StringBuilder tmp = new StringBuilder(count);
|
StringBuilder tmp = new StringBuilder(count);
|
||||||
int submode = initialSubmode;
|
int submode = initialSubmode;
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
char ch = msg.charAt(startpos + idx);
|
if (input.isECI(startpos + idx)) {
|
||||||
switch (submode) {
|
encodingECI(input.getECIValue(startpos + idx), sb);
|
||||||
case SUBMODE_ALPHA:
|
idx++;
|
||||||
if (isAlphaUpper(ch)) {
|
} else {
|
||||||
if (ch == ' ') {
|
char ch = input.charAt(startpos + idx);
|
||||||
tmp.append((char) 26); //space
|
switch (submode) {
|
||||||
} else {
|
case SUBMODE_ALPHA:
|
||||||
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 (isAlphaUpper(ch)) {
|
if (isAlphaUpper(ch)) {
|
||||||
tmp.append((char) 27); //as
|
if (ch == ' ') {
|
||||||
tmp.append((char) (ch - 65));
|
tmp.append((char) 26); //space
|
||||||
//space cannot happen here, it is also in "Lower"
|
} else {
|
||||||
break;
|
tmp.append((char) (ch - 65));
|
||||||
} else if (isMixed(ch)) {
|
}
|
||||||
submode = SUBMODE_MIXED;
|
} else {
|
||||||
tmp.append((char) 28); //ml
|
if (isAlphaLower(ch)) {
|
||||||
continue;
|
submode = SUBMODE_LOWER;
|
||||||
} else {
|
tmp.append((char) 27); //ll
|
||||||
tmp.append((char) 29); //ps
|
continue;
|
||||||
tmp.append((char) PUNCTUATION[ch]);
|
} else if (isMixed(ch)) {
|
||||||
break;
|
submode = SUBMODE_MIXED;
|
||||||
}
|
tmp.append((char) 28); //ml
|
||||||
}
|
continue;
|
||||||
break;
|
} else {
|
||||||
case SUBMODE_MIXED:
|
tmp.append((char) 29); //ps
|
||||||
if (isMixed(ch)) {
|
tmp.append((char) PUNCTUATION[ch]);
|
||||||
tmp.append((char) MIXED[ch]);
|
break;
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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;
|
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;
|
char h = 0;
|
||||||
|
@ -363,6 +401,56 @@ final class PDF417HighLevelEncoder {
|
||||||
return submode;
|
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),
|
* 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
|
* 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;
|
int idx = 0;
|
||||||
StringBuilder tmp = new StringBuilder(count / 3 + 1);
|
StringBuilder tmp = new StringBuilder(count / 3 + 1);
|
||||||
BigInteger num900 = BigInteger.valueOf(900);
|
BigInteger num900 = BigInteger.valueOf(900);
|
||||||
|
@ -424,7 +512,7 @@ final class PDF417HighLevelEncoder {
|
||||||
while (idx < count) {
|
while (idx < count) {
|
||||||
tmp.setLength(0);
|
tmp.setLength(0);
|
||||||
int len = Math.min(44, count - idx);
|
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);
|
BigInteger bigint = new BigInteger(part);
|
||||||
do {
|
do {
|
||||||
tmp.append((char) bigint.mod(num900).intValue());
|
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.
|
* Determines the number of consecutive characters that are encodable using numeric compaction.
|
||||||
*
|
*
|
||||||
* @param msg the message
|
* @param input the input
|
||||||
* @param startpos the start position within the message
|
* @param startpos the start position within the input
|
||||||
* @return the requested character count
|
* @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 count = 0;
|
||||||
int len = msg.length();
|
final int len = input.length();
|
||||||
int idx = startpos;
|
int idx = startpos;
|
||||||
if (idx < len) {
|
if (idx < len) {
|
||||||
char ch = msg.charAt(idx);
|
while (idx < len && !input.isECI(idx) && isDigit(input.charAt(idx))) {
|
||||||
while (isDigit(ch) && idx < len) {
|
|
||||||
count++;
|
count++;
|
||||||
idx++;
|
idx++;
|
||||||
if (idx < len) {
|
|
||||||
ch = msg.charAt(idx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
|
@ -491,22 +575,18 @@ final class PDF417HighLevelEncoder {
|
||||||
/**
|
/**
|
||||||
* Determines the number of consecutive characters that are encodable using text compaction.
|
* Determines the number of consecutive characters that are encodable using text compaction.
|
||||||
*
|
*
|
||||||
* @param msg the message
|
* @param input the input
|
||||||
* @param startpos the start position within the message
|
* @param startpos the start position within the input
|
||||||
* @return the requested character count
|
* @return the requested character count
|
||||||
*/
|
*/
|
||||||
private static int determineConsecutiveTextCount(CharSequence msg, int startpos) {
|
private static int determineConsecutiveTextCount(ECIInput input, int startpos) {
|
||||||
int len = msg.length();
|
final int len = input.length();
|
||||||
int idx = startpos;
|
int idx = startpos;
|
||||||
while (idx < len) {
|
while (idx < len) {
|
||||||
char ch = msg.charAt(idx);
|
|
||||||
int numericCount = 0;
|
int numericCount = 0;
|
||||||
while (numericCount < 13 && isDigit(ch) && idx < len) {
|
while (numericCount < 13 && idx < len && !input.isECI(idx) && isDigit(input.charAt(idx))) {
|
||||||
numericCount++;
|
numericCount++;
|
||||||
idx++;
|
idx++;
|
||||||
if (idx < len) {
|
|
||||||
ch = msg.charAt(idx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (numericCount >= 13) {
|
if (numericCount >= 13) {
|
||||||
return idx - startpos - numericCount;
|
return idx - startpos - numericCount;
|
||||||
|
@ -515,10 +595,9 @@ final class PDF417HighLevelEncoder {
|
||||||
//Heuristic: All text-encodable chars or digits are binary encodable
|
//Heuristic: All text-encodable chars or digits are binary encodable
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ch = msg.charAt(idx);
|
|
||||||
|
|
||||||
//Check if character is encodable
|
//Check if character is encodable
|
||||||
if (!isText(ch)) {
|
if (input.isECI(idx) || !isText(input.charAt(idx))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
idx++;
|
idx++;
|
||||||
|
@ -529,35 +608,35 @@ final class PDF417HighLevelEncoder {
|
||||||
/**
|
/**
|
||||||
* Determines the number of consecutive characters that are encodable using binary compaction.
|
* 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 startpos the start position within the message
|
||||||
* @param encoding the charset used to convert the message to a byte array
|
* @param encoding the charset used to convert the message to a byte array
|
||||||
* @return the requested character count
|
* @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 {
|
throws WriterException {
|
||||||
CharsetEncoder encoder = encoding.newEncoder();
|
CharsetEncoder encoder = encoding == null ? null : encoding.newEncoder();
|
||||||
int len = msg.length();
|
int len = input.length();
|
||||||
int idx = startpos;
|
int idx = startpos;
|
||||||
while (idx < len) {
|
while (idx < len) {
|
||||||
char ch = msg.charAt(idx);
|
|
||||||
int numericCount = 0;
|
int numericCount = 0;
|
||||||
|
|
||||||
while (numericCount < 13 && isDigit(ch)) {
|
int i = idx;
|
||||||
|
while (numericCount < 13 && !input.isECI(i) && isDigit(input.charAt(i))) {
|
||||||
numericCount++;
|
numericCount++;
|
||||||
//textCount++;
|
//textCount++;
|
||||||
int i = idx + numericCount;
|
i = idx + numericCount;
|
||||||
if (i >= len) {
|
if (i >= len) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ch = msg.charAt(i);
|
|
||||||
}
|
}
|
||||||
if (numericCount >= 13) {
|
if (numericCount >= 13) {
|
||||||
return idx - startpos;
|
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 + ')');
|
throw new WriterException("Non-encodable character detected: " + ch + " (Unicode: " + (int) ch + ')');
|
||||||
}
|
}
|
||||||
idx++;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,10 @@ public final class PDF417HighLevelEncoderTestAdapter {
|
||||||
private PDF417HighLevelEncoderTestAdapter() {
|
private PDF417HighLevelEncoderTestAdapter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String encodeHighLevel(String msg, Compaction compaction, Charset encoding) throws WriterException {
|
public static String encodeHighLevel(String msg,
|
||||||
return PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding);
|
Compaction compaction,
|
||||||
|
Charset encoding,
|
||||||
|
boolean autoECI) throws WriterException {
|
||||||
|
return PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding, autoECI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import com.google.zxing.pdf417.encoder.PDF417HighLevelEncoderTestAdapter;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
@ -283,21 +284,76 @@ public class PDF417DecoderTestCase extends Assert {
|
||||||
assertEquals(4190044, total);
|
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 {
|
private static void encodeDecode(String input, int expectedLength) throws WriterException, FormatException {
|
||||||
assertEquals(expectedLength, encodeDecode(input));
|
assertEquals(expectedLength, encodeDecode(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int encodeDecode(String input) throws WriterException, FormatException {
|
private static int encodeDecode(String input) throws WriterException, FormatException {
|
||||||
String s = PDF417HighLevelEncoderTestAdapter.encodeHighLevel(input, Compaction.AUTO, null);
|
return encodeDecode(input, null, false, true);
|
||||||
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());
|
private static int encodeDecode(String input, Charset charset, boolean autoECI, boolean decode)
|
||||||
return codewords.length;
|
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) {
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public final class PDF417EncoderTestCase extends Assert {
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeAuto() throws Exception {
|
public void testEncodeAuto() throws Exception {
|
||||||
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
||||||
"ABCD", Compaction.AUTO, StandardCharsets.UTF_8);
|
"ABCD", Compaction.AUTO, StandardCharsets.UTF_8, false);
|
||||||
assertEquals("\u039f\u001A\u0385ABCD", encoded);
|
assertEquals("\u039f\u001A\u0385ABCD", encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,33 +37,33 @@ public final class PDF417EncoderTestCase extends Assert {
|
||||||
public void testEncodeAutoWithSpecialChars() throws Exception {
|
public void testEncodeAutoWithSpecialChars() throws Exception {
|
||||||
// Just check if this does not throw an exception
|
// Just check if this does not throw an exception
|
||||||
PDF417HighLevelEncoder.encodeHighLevel(
|
PDF417HighLevelEncoder.encodeHighLevel(
|
||||||
"1%§s ?aG$", Compaction.AUTO, StandardCharsets.UTF_8);
|
"1%§s ?aG$", Compaction.AUTO, StandardCharsets.UTF_8, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeIso88591WithSpecialChars() throws Exception {
|
public void testEncodeIso88591WithSpecialChars() throws Exception {
|
||||||
// Just check if this does not throw an 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
|
@Test
|
||||||
public void testEncodeText() throws Exception {
|
public void testEncodeText() throws Exception {
|
||||||
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
||||||
"ABCD", Compaction.TEXT, StandardCharsets.UTF_8);
|
"ABCD", Compaction.TEXT, StandardCharsets.UTF_8, false);
|
||||||
assertEquals("Ο\u001A\u0001?", encoded);
|
assertEquals("Ο\u001A\u0001?", encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeNumeric() throws Exception {
|
public void testEncodeNumeric() throws Exception {
|
||||||
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
||||||
"1234", Compaction.NUMERIC, StandardCharsets.UTF_8);
|
"1234", Compaction.NUMERIC, StandardCharsets.UTF_8, false);
|
||||||
assertEquals("\u039f\u001A\u0386\f\u01b2", encoded);
|
assertEquals("\u039f\u001A\u0386\f\u01b2", encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeByte() throws Exception {
|
public void testEncodeByte() throws Exception {
|
||||||
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
String encoded = PDF417HighLevelEncoder.encodeHighLevel(
|
||||||
"abcd", Compaction.BYTE, StandardCharsets.UTF_8);
|
"abcd", Compaction.BYTE, StandardCharsets.UTF_8, false);
|
||||||
assertEquals("\u039f\u001A\u0385abcd", encoded);
|
assertEquals("\u039f\u001A\u0385abcd", encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue