下面列出了怎么用com.google.zxing.common.CharacterSetECI的API类实例代码及写法,或者点击链接到github查看源代码。
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (count << 3 > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException uce) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void a(BitSource bitsource, StringBuilder stringbuilder, int i, CharacterSetECI characterseteci, Collection collection, Map map)
{
if (i << 3 > bitsource.available())
{
throw FormatException.getFormatInstance();
}
byte abyte0[] = new byte[i];
for (int j = 0; j < i; j++)
{
abyte0[j] = (byte)bitsource.readBits(8);
}
String s;
if (characterseteci == null)
{
s = StringUtils.guessEncoding(abyte0, map);
} else
{
s = characterseteci.name();
}
try
{
stringbuilder.append(new String(abyte0, s));
}
catch (UnsupportedEncodingException unsupportedencodingexception)
{
throw FormatException.getFormatInstance();
}
collection.add(abyte0);
}
private static void decodeByteSegment(BitSource bits, StringBuilder result, int count,
CharacterSetECI currentCharacterSetECI, Collection<byte[]> byteSegments, Map<DecodeHintType, ?> hints)
throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits, StringBuilder result, int count, CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments, Map<DecodeHintType, ?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (count << 3 > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException uce) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits, StringBuilder result, int count, CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments, Map<DecodeHintType, ?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (count << 3 > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException uce) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
private static void decodeByteSegment(BitSource bits,
StringBuilder result,
int count,
CharacterSetECI currentCharacterSetECI,
Collection<byte[]> byteSegments,
Map<DecodeHintType,?> hints) throws FormatException {
// Don't crash trying to read more bits than we have available.
if (8 * count > bits.available()) {
throw FormatException.getFormatInstance();
}
byte[] readBytes = new byte[count];
for (int i = 0; i < count; i++) {
readBytes[i] = (byte) bits.readBits(8);
}
String encoding;
if (currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
encoding = StringUtils.guessEncoding(readBytes, hints);
} else {
encoding = currentCharacterSetECI.name();
}
try {
result.append(new String(readBytes, encoding));
} catch (UnsupportedEncodingException ignored) {
throw FormatException.getFormatInstance();
}
byteSegments.add(readBytes);
}
public static QRCode encode(String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
// Determine what character encoding has been specified by the caller, if any
String encoding = DEFAULT_BYTE_MODE_ENCODING;
boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET);
if (hasEncodingHint) {
encoding = hints.get(EncodeHintType.CHARACTER_SET).toString();
}
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient. Twould be nice.
Mode mode = chooseMode(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append ECI segment if applicable
if (mode == Mode.BYTE && (hasEncodingHint || !DEFAULT_BYTE_MODE_ENCODING.equals(encoding))) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
if (eci != null) {
appendECI(eci, headerBits);
}
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
Version version;
if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) {
int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString());
version = Version.getVersionForNumber(versionNumber);
int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version);
if (!willFit(bitsNeeded, version, ecLevel)) {
throw new WriterException("Data too big for requested version");
}
} else {
version = recommendVersion(ecLevel, mode, headerBits, dataBits);
}
BitArray headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
private static void appendECI(CharacterSetECI eci, BitArray bits) {
bits.appendBits(Mode.ECI.getBits(), 4);
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValue(), 8);
}
static DecoderResult decode(byte[] bytes,
Version version,
ErrorCorrectionLevel ecLevel,
Map<DecodeHintType,?> hints) throws FormatException {
BitSource bits = new BitSource(bytes);
StringBuilder result = new StringBuilder(50);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
Mode mode;
do {
// While still another segment to read...
if (bits.available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
mode = Mode.TERMINATOR;
} else {
mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
}
switch (mode) {
case TERMINATOR:
break;
case FNC1_FIRST_POSITION:
case FNC1_SECOND_POSITION:
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
case STRUCTURED_APPEND:
if (bits.available() < 16) {
throw FormatException.getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
symbolSequence = bits.readBits(8);
parityData = bits.readBits(8);
break;
case ECI:
// Count doesn't apply to ECI
int value = parseECIValue(bits);
currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
if (currentCharacterSetECI == null) {
throw FormatException.getFormatInstance();
}
break;
case HANZI:
// First handle Hanzi mode which does not start with character count
// Chinese mode contains a sub set indicator right after mode indicator
int subset = bits.readBits(4);
int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
if (subset == GB2312_SUBSET) {
decodeHanziSegment(bits, result, countHanzi);
}
break;
default:
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
int count = bits.readBits(mode.getCharacterCountBits(version));
switch (mode) {
case NUMERIC:
decodeNumericSegment(bits, result, count);
break;
case ALPHANUMERIC:
decodeAlphanumericSegment(bits, result, count, fc1InEffect);
break;
case BYTE:
decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
break;
case KANJI:
decodeKanjiSegment(bits, result, count);
break;
default:
throw FormatException.getFormatInstance();
}
break;
}
} while (mode != Mode.TERMINATOR);
} catch (IllegalArgumentException iae) {
// from readBits() calls
throw FormatException.getFormatInstance();
}
return new DecoderResult(bytes,
result.toString(),
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData);
}
public static QRCode encode(String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
// Determine what character encoding has been specified by the caller, if any
String encoding = DEFAULT_BYTE_MODE_ENCODING;
boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET);
if (hasEncodingHint) {
encoding = hints.get(EncodeHintType.CHARACTER_SET).toString();
}
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient. Twould be nice.
Mode mode = chooseMode(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append ECI segment if applicable
if (mode == Mode.BYTE && (hasEncodingHint || !DEFAULT_BYTE_MODE_ENCODING.equals(encoding))) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
if (eci != null) {
appendECI(eci, headerBits);
}
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
Version version;
if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) {
int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString());
version = Version.getVersionForNumber(versionNumber);
int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version);
if (!willFit(bitsNeeded, version, ecLevel)) {
throw new WriterException("Data too big for requested version");
}
} else {
version = recommendVersion(ecLevel, mode, headerBits, dataBits);
}
BitArray headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
private static void appendECI(CharacterSetECI eci, BitArray bits) {
bits.appendBits(Mode.ECI.getBits(), 4);
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValue(), 8);
}
static DecoderResult decode(byte[] bytes,
Version version,
ErrorCorrectionLevel ecLevel,
Map<DecodeHintType,?> hints) throws FormatException {
BitSource bits = new BitSource(bytes);
StringBuilder result = new StringBuilder(50);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
Mode mode;
do {
// While still another segment to read...
if (bits.available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
mode = Mode.TERMINATOR;
} else {
mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
}
switch (mode) {
case TERMINATOR:
break;
case FNC1_FIRST_POSITION:
case FNC1_SECOND_POSITION:
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
case STRUCTURED_APPEND:
if (bits.available() < 16) {
throw FormatException.getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
symbolSequence = bits.readBits(8);
parityData = bits.readBits(8);
break;
case ECI:
// Count doesn't apply to ECI
int value = parseECIValue(bits);
currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
if (currentCharacterSetECI == null) {
throw FormatException.getFormatInstance();
}
break;
case HANZI:
// First handle Hanzi mode which does not start with character count
// Chinese mode contains a sub set indicator right after mode indicator
int subset = bits.readBits(4);
int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
if (subset == GB2312_SUBSET) {
decodeHanziSegment(bits, result, countHanzi);
}
break;
default:
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
int count = bits.readBits(mode.getCharacterCountBits(version));
switch (mode) {
case NUMERIC:
decodeNumericSegment(bits, result, count);
break;
case ALPHANUMERIC:
decodeAlphanumericSegment(bits, result, count, fc1InEffect);
break;
case BYTE:
decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
break;
case KANJI:
decodeKanjiSegment(bits, result, count);
break;
default:
throw FormatException.getFormatInstance();
}
break;
}
} while (mode != Mode.TERMINATOR);
} catch (IllegalArgumentException iae) {
// from readBits() calls
throw FormatException.getFormatInstance();
}
return new DecoderResult(bytes,
result.toString(),
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData);
}
public static QRCode encode(String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
// Determine what character encoding has been specified by the caller, if any
String encoding = DEFAULT_BYTE_MODE_ENCODING;
boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET);
if (hasEncodingHint) {
encoding = hints.get(EncodeHintType.CHARACTER_SET).toString();
}
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient. Twould be nice.
Mode mode = chooseMode(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append ECI segment if applicable
if (mode == Mode.BYTE && (hasEncodingHint || !DEFAULT_BYTE_MODE_ENCODING.equals(encoding))) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
if (eci != null) {
appendECI(eci, headerBits);
}
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
Version version;
if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) {
int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString());
version = Version.getVersionForNumber(versionNumber);
int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version);
if (!willFit(bitsNeeded, version, ecLevel)) {
throw new WriterException("Data too big for requested version");
}
} else {
version = recommendVersion(ecLevel, mode, headerBits, dataBits);
}
BitArray headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
private static void appendECI(CharacterSetECI eci, BitArray bits) {
bits.appendBits(Mode.ECI.getBits(), 4);
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValue(), 8);
}
static DecoderResult decode(byte[] bytes,
Version version,
ErrorCorrectionLevel ecLevel,
Map<DecodeHintType,?> hints) throws FormatException {
BitSource bits = new BitSource(bytes);
StringBuilder result = new StringBuilder(50);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
Mode mode;
do {
// While still another segment to read...
if (bits.available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
mode = Mode.TERMINATOR;
} else {
mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
}
switch (mode) {
case TERMINATOR:
break;
case FNC1_FIRST_POSITION:
case FNC1_SECOND_POSITION:
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
case STRUCTURED_APPEND:
if (bits.available() < 16) {
throw FormatException.getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
symbolSequence = bits.readBits(8);
parityData = bits.readBits(8);
break;
case ECI:
// Count doesn't apply to ECI
int value = parseECIValue(bits);
currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
if (currentCharacterSetECI == null) {
throw FormatException.getFormatInstance();
}
break;
case HANZI:
// First handle Hanzi mode which does not start with character count
// Chinese mode contains a sub set indicator right after mode indicator
int subset = bits.readBits(4);
int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
if (subset == GB2312_SUBSET) {
decodeHanziSegment(bits, result, countHanzi);
}
break;
default:
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
int count = bits.readBits(mode.getCharacterCountBits(version));
switch (mode) {
case NUMERIC:
decodeNumericSegment(bits, result, count);
break;
case ALPHANUMERIC:
decodeAlphanumericSegment(bits, result, count, fc1InEffect);
break;
case BYTE:
decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
break;
case KANJI:
decodeKanjiSegment(bits, result, count);
break;
default:
throw FormatException.getFormatInstance();
}
break;
}
} while (mode != Mode.TERMINATOR);
} catch (IllegalArgumentException iae) {
// from readBits() calls
throw FormatException.getFormatInstance();
}
return new DecoderResult(bytes,
result.toString(),
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData);
}
/**
* Performs high-level encoding of a PDF417 message using the algorithm described in annex P
* of ISO/IEC 15438:2001(E). If byte compaction has been selected, then only byte compaction
* is used.
*
* @param msg the message
* @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
* @return the encoded message (the char values range from 0 to 928)
*/
static String encodeHighLevel(String msg, Compaction compaction, Charset encoding) throws WriterException {
//the codewords 0..928 are encoded as Unicode characters
StringBuilder sb = new StringBuilder(msg.length());
if (encoding != null && !DEFAULT_ENCODING_NAMES.contains(encoding.name())) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding.name());
if (eci != null) {
encodingECI(eci.getValue(), sb);
}
}
int len = msg.length();
int p = 0;
int textSubMode = SUBMODE_ALPHA;
// User selected encoding mode
byte[] bytes = null; //Fill later and only if needed
if (compaction == Compaction.TEXT) {
encodeText(msg, p, len, sb, textSubMode);
} else if (compaction == Compaction.BYTE) {
bytes = toBytes(msg, encoding);
encodeBinary(bytes, p, bytes.length, BYTE_COMPACTION, sb);
} else if (compaction == Compaction.NUMERIC) {
sb.append((char) LATCH_TO_NUMERIC);
encodeNumeric(msg, p, len, sb);
} else {
int encodingMode = TEXT_COMPACTION; //Default mode, see 4.4.2.1
while (p < len) {
int n = determineConsecutiveDigitCount(msg, p);
if (n >= 13) {
sb.append((char) LATCH_TO_NUMERIC);
encodingMode = NUMERIC_COMPACTION;
textSubMode = SUBMODE_ALPHA; //Reset after latch
encodeNumeric(msg, p, n, sb);
p += n;
} else {
int t = determineConsecutiveTextCount(msg, 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);
p += t;
} else {
if (bytes == null) {
bytes = toBytes(msg, encoding);
}
int b = determineConsecutiveBinaryCount(msg, bytes, p);
if (b == 0) {
b = 1;
}
if (b == 1 && encodingMode == TEXT_COMPACTION) {
//Switch for one byte (instead of latch)
encodeBinary(bytes, p, 1, TEXT_COMPACTION, sb);
} else {
//Mode latch performed by encodeBinary()
encodeBinary(bytes, p, b, encodingMode, sb);
encodingMode = BYTE_COMPACTION;
textSubMode = SUBMODE_ALPHA; //Reset after latch
}
p += b;
}
}
}
}
return sb.toString();
}
public static QRCode encode(String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
// Determine what character encoding has been specified by the caller, if any
String encoding = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
if (encoding == null) {
encoding = DEFAULT_BYTE_MODE_ENCODING;
}
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient. Twould be nice.
Mode mode = chooseMode(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append ECI segment if applicable
if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
if (eci != null) {
appendECI(eci, headerBits);
}
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
// Hard part: need to know version to know how many bits length takes. But need to know how many
// bits it takes to know version. First we take a guess at version by assuming version will be
// the minimum, 1:
int provisionalBitsNeeded = headerBits.getSize()
+ mode.getCharacterCountBits(Version.getVersionForNumber(1))
+ dataBits.getSize();
Version provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel);
// Use that guess to calculate the right version. I am still not sure this works in 100% of cases.
int bitsNeeded = headerBits.getSize()
+ mode.getCharacterCountBits(provisionalVersion)
+ dataBits.getSize();
Version version = chooseVersion(bitsNeeded, ecLevel);
BitArray headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
private static void appendECI(CharacterSetECI eci, BitArray bits) {
bits.appendBits(Mode.ECI.getBits(), 4);
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValue(), 8);
}
static DecoderResult decode(byte[] bytes,
Version version,
ErrorCorrectionLevel ecLevel,
Map<DecodeHintType,?> hints) throws FormatException {
BitSource bits = new BitSource(bytes);
StringBuilder result = new StringBuilder(50);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
Mode mode;
do {
// While still another segment to read...
if (bits.available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
mode = Mode.TERMINATOR;
} else {
mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
}
if (mode != Mode.TERMINATOR) {
if (mode == Mode.FNC1_FIRST_POSITION || mode == Mode.FNC1_SECOND_POSITION) {
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
} else if (mode == Mode.STRUCTURED_APPEND) {
if (bits.available() < 16) {
throw FormatException.getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
symbolSequence = bits.readBits(8);
parityData = bits.readBits(8);
} else if (mode == Mode.ECI) {
// Count doesn't apply to ECI
int value = parseECIValue(bits);
currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
if (currentCharacterSetECI == null) {
throw FormatException.getFormatInstance();
}
} else {
// First handle Hanzi mode which does not start with character count
if (mode == Mode.HANZI) {
//chinese mode contains a sub set indicator right after mode indicator
int subset = bits.readBits(4);
int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
if (subset == GB2312_SUBSET) {
decodeHanziSegment(bits, result, countHanzi);
}
} else {
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
int count = bits.readBits(mode.getCharacterCountBits(version));
if (mode == Mode.NUMERIC) {
decodeNumericSegment(bits, result, count);
} else if (mode == Mode.ALPHANUMERIC) {
decodeAlphanumericSegment(bits, result, count, fc1InEffect);
} else if (mode == Mode.BYTE) {
decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
} else if (mode == Mode.KANJI) {
decodeKanjiSegment(bits, result, count);
} else {
throw FormatException.getFormatInstance();
}
}
}
}
} while (mode != Mode.TERMINATOR);
} catch (IllegalArgumentException iae) {
// from readBits() calls
throw FormatException.getFormatInstance();
}
return new DecoderResult(bytes,
result.toString(),
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData);
}
static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException {
StringBuilder result = new StringBuilder(codewords.length * 2);
Charset encoding = DEFAULT_ENCODING;
// Get compaction mode
int codeIndex = 1;
int code = codewords[codeIndex++];
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
while (codeIndex < codewords[0]) {
switch (code) {
case TEXT_COMPACTION_MODE_LATCH:
codeIndex = textCompaction(codewords, codeIndex, result);
break;
case BYTE_COMPACTION_MODE_LATCH:
case BYTE_COMPACTION_MODE_LATCH_6:
codeIndex = byteCompaction(code, codewords, encoding, codeIndex, result);
break;
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
result.append((char) codewords[codeIndex++]);
break;
case NUMERIC_COMPACTION_MODE_LATCH:
codeIndex = numericCompaction(codewords, codeIndex, result);
break;
case ECI_CHARSET:
CharacterSetECI charsetECI =
CharacterSetECI.getCharacterSetECIByValue(codewords[codeIndex++]);
encoding = Charset.forName(charsetECI.name());
break;
case ECI_GENERAL_PURPOSE:
// Can't do anything with generic ECI; skip its 2 characters
codeIndex += 2;
break;
case ECI_USER_DEFINED:
// Can't do anything with user ECI; skip its 1 character
codeIndex ++;
break;
case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata);
break;
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
case MACRO_PDF417_TERMINATOR:
// Should not see these outside a macro block
throw FormatException.getFormatInstance();
default:
// Default to text compaction. During testing numerous barcodes
// appeared to be missing the starting mode. In these cases defaulting
// to text compaction seems to work.
codeIndex--;
codeIndex = textCompaction(codewords, codeIndex, result);
break;
}
if (codeIndex < codewords.length) {
code = codewords[codeIndex++];
} else {
throw FormatException.getFormatInstance();
}
}
if (result.length() == 0) {
throw FormatException.getFormatInstance();
}
DecoderResult decoderResult = new DecoderResult(null, result.toString(), null, ecLevel);
decoderResult.setOther(resultMetadata);
return decoderResult;
}
public static QRCode encode(String content,
ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
// Determine what character encoding has been specified by the caller, if any
String encoding = DEFAULT_BYTE_MODE_ENCODING;
if (hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET)) {
encoding = hints.get(EncodeHintType.CHARACTER_SET).toString();
}
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient. Twould be nice.
Mode mode = chooseMode(content, encoding);
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append ECI segment if applicable
if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) {
CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
if (eci != null) {
appendECI(eci, headerBits);
}
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
appendBytes(content, mode, dataBits, encoding);
// Hard part: need to know version to know how many bits length takes. But need to know how many
// bits it takes to know version. First we take a guess at version by assuming version will be
// the minimum, 1:
int provisionalBitsNeeded = headerBits.getSize()
+ mode.getCharacterCountBits(Version.getVersionForNumber(1))
+ dataBits.getSize();
Version provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel);
// Use that guess to calculate the right version. I am still not sure this works in 100% of cases.
int bitsNeeded = headerBits.getSize()
+ mode.getCharacterCountBits(provisionalVersion)
+ dataBits.getSize();
Version version = chooseVersion(bitsNeeded, ecLevel);
BitArray headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
private static void appendECI(CharacterSetECI eci, BitArray bits) {
bits.appendBits(Mode.ECI.getBits(), 4);
// This is correct for values up to 127, which is all we need now.
bits.appendBits(eci.getValue(), 8);
}