summaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
authorAlexGeller1 <87009702+AlexGeller1@users.noreply.github.com>2022-03-05 17:07:37 +0300
committerGitHub <noreply@github.com>2022-03-05 17:07:37 +0300
commit92854d4a5553465ff42e68927f3a161bb8977e98 (patch)
tree71a36ad3199acb603d3a284c1031756d1ff7bdff /core/src/main
parent4bd257e8c5577036b38352082bde646045bf95ee (diff)
downloadzxing-92854d4a5553465ff42e68927f3a161bb8977e98.tar.xz
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
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/java/com/google/zxing/EncodeHintType.java10
-rwxr-xr-xcore/src/main/java/com/google/zxing/common/ECIInput.java107
-rwxr-xr-xcore/src/main/java/com/google/zxing/common/MinimalECIInput.java325
-rwxr-xr-xcore/src/main/java/com/google/zxing/datamatrix/encoder/MinimalEncoder.java172
-rw-r--r--core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java10
-rw-r--r--core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java23
-rw-r--r--core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java12
-rw-r--r--core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java377
-rw-r--r--core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoderTestAdapter.java7
9 files changed, 730 insertions, 313 deletions
diff --git a/core/src/main/java/com/google/zxing/EncodeHintType.java b/core/src/main/java/com/google/zxing/EncodeHintType.java
index 2c68f2af2..f94b575a5 100644
--- a/core/src/main/java/com/google/zxing/EncodeHintType.java
+++ b/core/src/main/java/com/google/zxing/EncodeHintType.java
@@ -104,6 +104,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.
* 0 indicates to use the minimum number of layers (the default).
diff --git a/core/src/main/java/com/google/zxing/common/ECIInput.java b/core/src/main/java/com/google/zxing/common/ECIInput.java
new file mode 100755
index 000000000..ad444e13c
--- /dev/null
+++ b/core/src/main/java/com/google/zxing/common/ECIInput.java
@@ -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);
+}
diff --git a/core/src/main/java/com/google/zxing/common/MinimalECIInput.java b/core/src/main/java/com/google/zxing/common/MinimalECIInput.java
new file mode 100755
index 000000000..ac204b832
--- /dev/null
+++ b/core/src/main/java/com/google/zxing/common/MinimalECIInput.java
@@ -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;
+ }
+
+ }
+}
diff --git a/core/src/main/java/com/google/zxing/datamatrix/encoder/MinimalEncoder.java b/core/src/main/java/com/google/zxing/datamatrix/encoder/MinimalEncoder.java
index 284d5d646..4d3dd8d3c 100755
--- a/core/src/main/java/com/google/zxing/datamatrix/encoder/MinimalEncoder.java
+++ b/core/src/main/java/com/google/zxing/datamatrix/encoder/MinimalEncoder.java
@@ -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;
- }
-
- }
}
}
diff --git a/core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java b/core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java
index 273e092bd..80e6ff7b3 100644
--- a/core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java
+++ b/core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java
@@ -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);
diff --git a/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java b/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
index f732edd92..ab52e665d 100644
--- a/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
+++ b/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
@@ -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 {
diff --git a/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java b/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java
index 8469c9e77..a9d63dc23 100644
--- a/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java
+++ b/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java
@@ -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);
diff --git a/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java b/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java
index e58b2132e..607dea9d7 100644
--- a/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java
+++ b/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java
@@ -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
+ 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)) {
+ if (ch == ' ') {
+ tmp.append((char) 26); //space
+ } else {
+ tmp.append((char) (ch - 65));
+ }
} else {
- tmp.append((char) (ch - 65));
+ 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;
+ }
}
- } else {
+ break;
+ case SUBMODE_LOWER:
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;
+ if (ch == ' ') {
+ tmp.append((char) 26); //space
+ } else {
+ tmp.append((char) (ch - 97));
+ }
} else {
- tmp.append((char) 29); //ps
- tmp.append((char) PUNCTUATION[ch]);
- break;
+ 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_LOWER:
- if (isAlphaLower(ch)) {
- if (ch == ' ') {
- tmp.append((char) 26); //space
+ break;
+ case SUBMODE_MIXED:
+ if (isMixed(ch)) {
+ tmp.append((char) MIXED[ch]);
} else {
- tmp.append((char) (ch - 97));
+ 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]);
+ }
}
- } 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
+ break;
+ default: //SUBMODE_PUNCTUATION
+ if (isPunctuation(ch)) {
tmp.append((char) PUNCTUATION[ch]);
- break;
- }
- }
- break;
- case SUBMODE_MIXED:
- if (isMixed(ch)) {
- tmp.append((char) MIXED[ch]);
- } else {
- if (isAlphaUpper(ch)) {
+ } else {
submode = SUBMODE_ALPHA;
- tmp.append((char) 28); //al
- continue;
- } else if (isAlphaLower(ch)) {
- submode = SUBMODE_LOWER;
- tmp.append((char) 27); //ll
+ tmp.append((char) 29); //al
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]);
}
- }
+ }
+ 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;
@@ -364,6 +402,56 @@ final class PDF417HighLevelEncoder {
}
/**
+ * 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
* codepage.
@@ -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;
+ }
+ }
}
diff --git a/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoderTestAdapter.java b/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoderTestAdapter.java
index ac6092a52..6ba7992d6 100644
--- a/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoderTestAdapter.java
+++ b/core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoderTestAdapter.java
@@ -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);
}
}