2 * Copyright 2008 ZXing authors
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.google.zxing.qrcode.encoder;
19 import com.google.zxing.WriterException;
20 import com.google.zxing.common.BitArray;
21 import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
22 import com.google.zxing.qrcode.decoder.Mode;
23 import junit.framework.TestCase;
25 import java.io.UnsupportedEncodingException;
28 * @author satorux@google.com (Satoru Takabayashi) - creator
29 * @author mysen@google.com (Chris Mysen) - ported from C++
31 public final class EncoderTestCase extends TestCase {
33 public void testGetAlphanumericCode() {
34 // The first ten code points are numbers.
35 for (int i = 0; i < 10; ++i) {
36 assertEquals(i, Encoder.getAlphanumericCode('0' + i));
39 // The next 26 code points are capital alphabet letters.
40 for (int i = 10; i < 36; ++i) {
41 assertEquals(i, Encoder.getAlphanumericCode('A' + i - 10));
44 // Others are symbol letters
45 assertEquals(36, Encoder.getAlphanumericCode(' '));
46 assertEquals(37, Encoder.getAlphanumericCode('$'));
47 assertEquals(38, Encoder.getAlphanumericCode('%'));
48 assertEquals(39, Encoder.getAlphanumericCode('*'));
49 assertEquals(40, Encoder.getAlphanumericCode('+'));
50 assertEquals(41, Encoder.getAlphanumericCode('-'));
51 assertEquals(42, Encoder.getAlphanumericCode('.'));
52 assertEquals(43, Encoder.getAlphanumericCode('/'));
53 assertEquals(44, Encoder.getAlphanumericCode(':'));
55 // Should return -1 for other letters;
56 assertEquals(-1, Encoder.getAlphanumericCode('a'));
57 assertEquals(-1, Encoder.getAlphanumericCode('#'));
58 assertEquals(-1, Encoder.getAlphanumericCode('\0'));
61 public void testChooseMode() throws WriterException {
63 assertSame(Mode.NUMERIC, Encoder.chooseMode("0"));
64 assertSame(Mode.NUMERIC, Encoder.chooseMode("0123456789"));
66 assertSame(Mode.ALPHANUMERIC, Encoder.chooseMode("A"));
67 assertSame(Mode.ALPHANUMERIC,
68 Encoder.chooseMode("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"));
70 assertSame(Mode.BYTE, Encoder.chooseMode("a"));
71 assertSame(Mode.BYTE, Encoder.chooseMode("#"));
72 assertSame(Mode.BYTE, Encoder.chooseMode(""));
73 // Kanji mode. We used to use MODE_KANJI for these, but we stopped
74 // doing that as we cannot distinguish Shift_JIS from other encodings
75 // from data bytes alone. See also comments in qrcode_encoder.h.
77 // AIUE in Hiragana in Shift_JIS
79 Encoder.chooseMode(shiftJISString(new byte[]{0x8, 0xa, 0x8, 0xa, 0x8, 0xa, 0x8, (byte) 0xa6})));
81 // Nihon in Kanji in Shift_JIS.
82 assertSame(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[]{0x9, 0xf, 0x9, 0x7b})));
84 // Sou-Utsu-Byou in Kanji in Shift_JIS.
85 assertSame(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[]{0xe, 0x4, 0x9, 0x5, 0x9, 0x61})));
88 public void testEncode() throws WriterException {
89 QRCode qrCode = new QRCode();
90 Encoder.encode("ABCDEF", ErrorCorrectionLevel.H, qrCode);
91 // The following is a valid QR Code that can be read by cell phones.
94 " mode: ALPHANUMERIC\n" +
97 " matrixWidth: 21\n" +
99 " numTotalBytes: 26\n" +
100 " numDataBytes: 9\n" +
101 " numECBytes: 17\n" +
102 " numRSBlocks: 1\n" +
104 " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1\n" +
105 " 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1\n" +
106 " 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 1 1 0 1\n" +
107 " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" +
108 " 1 0 1 1 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1\n" +
109 " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1\n" +
110 " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" +
111 " 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0\n" +
112 " 0 0 1 0 1 1 1 0 1 1 0 0 1 1 0 0 0 1 0 0 1\n" +
113 " 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0\n" +
114 " 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 1 0\n" +
115 " 1 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 0\n" +
116 " 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0\n" +
117 " 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0\n" +
118 " 1 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1\n" +
119 " 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1\n" +
120 " 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 0 1\n" +
121 " 1 0 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 1 0 1 0\n" +
122 " 1 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1\n" +
123 " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 1\n" +
124 " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1\n" +
126 assertEquals(expected, qrCode.toString());
129 public void testAppendModeInfo() {
130 BitArray bits = new BitArray();
131 Encoder.appendModeInfo(Mode.NUMERIC, bits);
132 assertEquals(" ...X", bits.toString());
135 public void testAppendLengthInfo() throws WriterException {
137 BitArray bits = new BitArray();
138 Encoder.appendLengthInfo(1, // 1 letter (1/1).
142 assertEquals(" ........ .X", bits.toString()); // 10 bits.
145 BitArray bits = new BitArray();
146 Encoder.appendLengthInfo(2, // 2 letters (2/1).
150 assertEquals(" ........ .X.", bits.toString()); // 11 bits.
153 BitArray bits = new BitArray();
154 Encoder.appendLengthInfo(255, // 255 letter (255/1).
158 assertEquals(" ........ XXXXXXXX", bits.toString()); // 16 bits.
161 BitArray bits = new BitArray();
162 Encoder.appendLengthInfo(512, // 512 letters (1024/2).
166 assertEquals(" ..X..... ....", bits.toString()); // 12 bits.
170 public void testAppendBytes() throws WriterException {
172 // Should use appendNumericBytes.
173 // 1 = 01 = 0001 in 4 bits.
174 BitArray bits = new BitArray();
175 Encoder.appendBytes("1", Mode.NUMERIC, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
176 assertEquals(" ...X" , bits.toString());
179 // Should use appendAlphanumericBytes.
180 // A = 10 = 0xa = 001010 in 6 bits
181 BitArray bits = new BitArray();
182 Encoder.appendBytes("A", Mode.ALPHANUMERIC, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
183 assertEquals(" ..X.X." , bits.toString());
184 // Lower letters such as 'a' cannot be encoded in MODE_ALPHANUMERIC.
186 Encoder.appendBytes("a", Mode.ALPHANUMERIC, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
187 } catch (WriterException we) {
192 // Should use append8BitBytes.
194 BitArray bits = new BitArray();
195 Encoder.appendBytes("abc", Mode.BYTE, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
196 assertEquals(" .XX....X .XX...X. .XX...XX", bits.toString());
197 // Anything can be encoded in QRCode.MODE_8BIT_BYTE.
198 Encoder.appendBytes("\0", Mode.BYTE, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
201 // Should use appendKanjiBytes.
203 BitArray bits = new BitArray();
204 Encoder.appendBytes(shiftJISString(new byte[] {(byte)0x93,0x5f}), Mode.KANJI, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
205 assertEquals(" .XX.XX.. XXXXX", bits.toString());
209 public void testTerminateBits() throws WriterException {
211 BitArray v = new BitArray();
212 Encoder.terminateBits(0, v);
213 assertEquals("", v.toString());
216 BitArray v = new BitArray();
217 Encoder.terminateBits(1, v);
218 assertEquals(" ........", v.toString());
221 BitArray v = new BitArray();
222 v.appendBits(0, 3); // Append 000
223 Encoder.terminateBits(1, v);
224 assertEquals(" ........", v.toString());
227 BitArray v = new BitArray();
228 v.appendBits(0, 5); // Append 00000
229 Encoder.terminateBits(1, v);
230 assertEquals(" ........", v.toString());
233 BitArray v = new BitArray();
234 v.appendBits(0, 8); // Append 00000000
235 Encoder.terminateBits(1, v);
236 assertEquals(" ........", v.toString());
239 BitArray v = new BitArray();
240 Encoder.terminateBits(2, v);
241 assertEquals(" ........ XXX.XX..", v.toString());
244 BitArray v = new BitArray();
245 v.appendBits(0, 1); // Append 0
246 Encoder.terminateBits(3, v);
247 assertEquals(" ........ XXX.XX.. ...X...X", v.toString());
251 public void testGetNumDataBytesAndNumECBytesForBlockID() throws WriterException {
252 int[] numDataBytes = new int[1];
253 int[] numEcBytes = new int[1];
255 Encoder.getNumDataBytesAndNumECBytesForBlockID(26, 9, 1, 0, numDataBytes, numEcBytes);
256 assertEquals(9, numDataBytes[0]);
257 assertEquals(17, numEcBytes[0]);
259 // Version 3-H. 2 blocks.
260 Encoder.getNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 0, numDataBytes, numEcBytes);
261 assertEquals(13, numDataBytes[0]);
262 assertEquals(22, numEcBytes[0]);
263 Encoder.getNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 1, numDataBytes, numEcBytes);
264 assertEquals(13, numDataBytes[0]);
265 assertEquals(22, numEcBytes[0]);
267 // Version 7-H. (4 + 1) blocks.
268 Encoder.getNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 0, numDataBytes, numEcBytes);
269 assertEquals(13, numDataBytes[0]);
270 assertEquals(26, numEcBytes[0]);
271 Encoder.getNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 4, numDataBytes, numEcBytes);
272 assertEquals(14, numDataBytes[0]);
273 assertEquals(26, numEcBytes[0]);
275 // Version 40-H. (20 + 61) blocks.
276 Encoder.getNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 0, numDataBytes, numEcBytes);
277 assertEquals(15, numDataBytes[0]);
278 assertEquals(30, numEcBytes[0]);
279 Encoder.getNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 20, numDataBytes, numEcBytes);
280 assertEquals(16, numDataBytes[0]);
281 assertEquals(30, numEcBytes[0]);
282 Encoder.getNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 80, numDataBytes, numEcBytes);
283 assertEquals(16, numDataBytes[0]);
284 assertEquals(30, numEcBytes[0]);
287 public void testInterleaveWithECBytes() throws WriterException {
289 byte[] dataBytes = {32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236};
290 BitArray in = new BitArray();
291 for (byte dataByte: dataBytes) {
292 in.appendBits(dataByte, 8);
294 BitArray out = new BitArray();
295 Encoder.interleaveWithECBytes(in, 26, 9, 1, out);
298 32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236,
299 // Error correction bytes.
300 42, (byte)159, 74, (byte)221, (byte)244, (byte)169, (byte)239, (byte)150, (byte)138, 70,
301 (byte)237, 85, (byte)224, 96, 74, (byte)219, 61,
303 assertEquals(expected.length, out.getSizeInBytes());
304 byte[] outArray = new byte[expected.length];
305 out.toBytes(0, outArray, 0, expected.length);
306 // Can't use Arrays.equals(), because outArray may be longer than out.sizeInBytes()
307 for (int x = 0; x < expected.length; x++) {
308 assertEquals(expected[x], outArray[x]);
311 // Numbers are from http://www.swetake.com/qr/qr8.html
314 67, 70, 22, 38, 54, 70, 86, 102, 118, (byte)134, (byte)150, (byte)166, (byte)182,
315 (byte)198, (byte)214, (byte)230, (byte)247, 7, 23, 39, 55, 71, 87, 103, 119, (byte)135,
316 (byte)151, (byte)166, 22, 38, 54, 70, 86, 102, 118, (byte)134, (byte)150, (byte)166,
317 (byte)182, (byte)198, (byte)214, (byte)230, (byte)247, 7, 23, 39, 55, 71, 87, 103, 119,
318 (byte)135, (byte)151, (byte)160, (byte)236, 17, (byte)236, 17, (byte)236, 17, (byte)236,
321 BitArray in = new BitArray();
322 for (byte dataByte: dataBytes) {
323 in.appendBits(dataByte, 8);
325 BitArray out = new BitArray();
326 Encoder.interleaveWithECBytes(in, 134, 62, 4, out);
329 67, (byte)230, 54, 55, 70, (byte)247, 70, 71, 22, 7, 86, 87, 38, 23, 102, 103, 54, 39,
330 118, 119, 70, 55, (byte)134, (byte)135, 86, 71, (byte)150, (byte)151, 102, 87, (byte)166,
331 (byte)160, 118, 103, (byte)182, (byte)236, (byte)134, 119, (byte)198, 17, (byte)150,
332 (byte)135, (byte)214, (byte)236, (byte)166, (byte)151, (byte)230, 17, (byte)182,
333 (byte)166, (byte)247, (byte)236, (byte)198, 22, 7, 17, (byte)214, 38, 23, (byte)236, 39,
335 // Error correction bytes.
336 (byte)175, (byte)155, (byte)245, (byte)236, 80, (byte)146, 56, 74, (byte)155, (byte)165,
337 (byte)133, (byte)142, 64, (byte)183, (byte)132, 13, (byte)178, 54, (byte)132, 108, 45,
338 113, 53, 50, (byte)214, 98, (byte)193, (byte)152, (byte)233, (byte)147, 50, 71, 65,
339 (byte)190, 82, 51, (byte)209, (byte)199, (byte)171, 54, 12, 112, 57, 113, (byte)155, 117,
340 (byte)211, (byte)164, 117, 30, (byte)158, (byte)225, 31, (byte)190, (byte)242, 38,
341 (byte)140, 61, (byte)179, (byte)154, (byte)214, (byte)138, (byte)147, 87, 27, 96, 77, 47,
342 (byte)187, 49, (byte)156, (byte)214,
344 assertEquals(expected.length, out.getSizeInBytes());
345 byte[] outArray = new byte[expected.length];
346 out.toBytes(0, outArray, 0, expected.length);
347 for (int x = 0; x < expected.length; x++) {
348 assertEquals(expected[x], outArray[x]);
353 public void testAppendNumericBytes() {
355 // 1 = 01 = 0001 in 4 bits.
356 BitArray bits = new BitArray();
357 Encoder.appendNumericBytes("1", bits);
358 assertEquals(" ...X" , bits.toString());
361 // 12 = 0xc = 0001100 in 7 bits.
362 BitArray bits = new BitArray();
363 Encoder.appendNumericBytes("12", bits);
364 assertEquals(" ...XX.." , bits.toString());
367 // 123 = 0x7b = 0001111011 in 10 bits.
368 BitArray bits = new BitArray();
369 Encoder.appendNumericBytes("123", bits);
370 assertEquals(" ...XXXX. XX" , bits.toString());
373 // 1234 = "123" + "4" = 0001111011 + 0100
374 BitArray bits = new BitArray();
375 Encoder.appendNumericBytes("1234", bits);
376 assertEquals(" ...XXXX. XX.X.." , bits.toString());
380 BitArray bits = new BitArray();
381 Encoder.appendNumericBytes("", bits);
382 assertEquals("" , bits.toString());
386 public void testAppendAlphanumericBytes() throws WriterException {
388 // A = 10 = 0xa = 001010 in 6 bits
389 BitArray bits = new BitArray();
390 Encoder.appendAlphanumericBytes("A", bits);
391 assertEquals(" ..X.X." , bits.toString());
394 // AB = 10 * 45 + 11 = 461 = 0x1cd = 00111001101 in 11 bits
395 BitArray bits = new BitArray();
396 Encoder.appendAlphanumericBytes("AB", bits);
397 assertEquals(" ..XXX..X X.X", bits.toString());
400 // ABC = "AB" + "C" = 00111001101 + 001100
401 BitArray bits = new BitArray();
402 Encoder.appendAlphanumericBytes("ABC", bits);
403 assertEquals(" ..XXX..X X.X..XX. ." , bits.toString());
407 BitArray bits = new BitArray();
408 Encoder.appendAlphanumericBytes("", bits);
409 assertEquals("" , bits.toString());
413 BitArray bits = new BitArray();
415 Encoder.appendAlphanumericBytes("abc", bits);
416 } catch (WriterException we) {
422 public void testAppend8BitBytes() throws WriterException {
425 BitArray bits = new BitArray();
426 Encoder.append8BitBytes("abc", bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
427 assertEquals(" .XX....X .XX...X. .XX...XX", bits.toString());
431 BitArray bits = new BitArray();
432 Encoder.append8BitBytes("", bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
433 assertEquals("", bits.toString());
437 // Numbers are from page 21 of JISX0510:2004
438 public void testAppendKanjiBytes() throws WriterException {
439 BitArray bits = new BitArray();
440 Encoder.appendKanjiBytes(shiftJISString(new byte[] {(byte)0x93,0x5f}), bits);
441 assertEquals(" .XX.XX.. XXXXX", bits.toString());
442 Encoder.appendKanjiBytes(shiftJISString(new byte[] {(byte)0xe4,(byte)0xaa}), bits);
443 assertEquals(" .XX.XX.. XXXXXXX. X.X.X.X. X.", bits.toString());
446 // Numbers are from http://www.swetake.com/qr/qr3.html and
447 // http://www.swetake.com/qr/qr9.html
448 public void testGenerateECBytes() {
450 byte[] dataBytes = {32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236};
451 byte[] ecBytes = Encoder.generateECBytes(dataBytes, 17);
453 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61
455 assertEquals(expected.length, ecBytes.length);
456 for (int x = 0; x < expected.length; x++) {
457 assertEquals(expected[x], ecBytes[x] & 0xFF);
461 byte[] dataBytes = {67, 70, 22, 38, 54, 70, 86, 102, 118,
462 (byte)134, (byte)150, (byte)166, (byte)182, (byte)198, (byte)214};
463 byte[] ecBytes = Encoder.generateECBytes(dataBytes, 18);
465 175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187
467 assertEquals(expected.length, ecBytes.length);
468 for (int x = 0; x < expected.length; x++) {
469 assertEquals(expected[x], ecBytes[x] & 0xFF);
473 // High-order zero coefficient case.
474 byte[] dataBytes = {32, 49, (byte)205, 69, 42, 20, 0, (byte)236, 17};
475 byte[] ecBytes = Encoder.generateECBytes(dataBytes, 17);
477 0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213
479 assertEquals(expected.length, ecBytes.length);
480 for (int x = 0; x < expected.length; x++) {
481 assertEquals(expected[x], ecBytes[x] & 0xFF);
486 public void testBugInBitVectorNumBytes() throws WriterException {
487 // There was a bug in BitVector.sizeInBytes() that caused it to return a
488 // smaller-by-one value (ex. 1465 instead of 1466) if the number of bits
489 // in the vector is not 8-bit aligned. In QRCodeEncoder::InitQRCode(),
490 // BitVector::sizeInBytes() is used for finding the smallest QR Code
491 // version that can fit the given data. Hence there were corner cases
492 // where we chose a wrong QR Code version that cannot fit the given
493 // data. Note that the issue did not occur with MODE_8BIT_BYTE, as the
494 // bits in the bit vector are always 8-bit aligned.
496 // Before the bug was fixed, the following test didn't pass, because:
498 // - MODE_NUMERIC is chosen as all bytes in the data are '0'
499 // - The 3518-byte numeric data needs 1466 bytes
500 // - 3518 / 3 * 10 + 7 = 11727 bits = 1465.875 bytes
501 // - 3 numeric bytes are encoded in 10 bits, hence the first
502 // 3516 bytes are encoded in 3516 / 3 * 10 = 11720 bits.
503 // - 2 numeric bytes can be encoded in 7 bits, hence the last
504 // 2 bytes are encoded in 7 bits.
505 // - The version 27 QR Code with the EC level L has 1468 bytes for data.
506 // - 1828 - 360 = 1468
507 // - In InitQRCode(), 3 bytes are reserved for a header. Hence 1465 bytes
508 // (1468 -3) are left for data.
509 // - Because of the bug in BitVector::sizeInBytes(), InitQRCode() determines
510 // the given data can fit in 1465 bytes, despite it needs 1466 bytes.
511 // - Hence QRCodeEncoder.encode() failed and returned false.
512 // - To be precise, it needs 11727 + 4 (getMode info) + 14 (length info) =
513 // 11745 bits = 1468.125 bytes are needed (i.e. cannot fit in 1468
515 StringBuilder builder = new StringBuilder(3518);
516 for (int x = 0; x < 3518; x++) {
519 QRCode qrCode = new QRCode();
520 Encoder.encode(builder.toString(), ErrorCorrectionLevel.L, qrCode);
523 private static String shiftJISString(byte[] bytes) throws WriterException {
525 return new String(bytes, "Shift_JIS");
526 } catch (UnsupportedEncodingException uee) {
527 throw new WriterException(uee.toString());