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 org.junit.Assert;
24 import org.junit.Test;
26 import java.io.UnsupportedEncodingException;
29 * @author satorux@google.com (Satoru Takabayashi) - creator
30 * @author mysen@google.com (Chris Mysen) - ported from C++
32 public final class EncoderTestCase extends Assert {
35 public void testGetAlphanumericCode() {
36 // The first ten code points are numbers.
37 for (int i = 0; i < 10; ++i) {
38 assertEquals(i, Encoder.getAlphanumericCode('0' + i));
41 // The next 26 code points are capital alphabet letters.
42 for (int i = 10; i < 36; ++i) {
43 assertEquals(i, Encoder.getAlphanumericCode('A' + i - 10));
46 // Others are symbol letters
47 assertEquals(36, Encoder.getAlphanumericCode(' '));
48 assertEquals(37, Encoder.getAlphanumericCode('$'));
49 assertEquals(38, Encoder.getAlphanumericCode('%'));
50 assertEquals(39, Encoder.getAlphanumericCode('*'));
51 assertEquals(40, Encoder.getAlphanumericCode('+'));
52 assertEquals(41, Encoder.getAlphanumericCode('-'));
53 assertEquals(42, Encoder.getAlphanumericCode('.'));
54 assertEquals(43, Encoder.getAlphanumericCode('/'));
55 assertEquals(44, Encoder.getAlphanumericCode(':'));
57 // Should return -1 for other letters;
58 assertEquals(-1, Encoder.getAlphanumericCode('a'));
59 assertEquals(-1, Encoder.getAlphanumericCode('#'));
60 assertEquals(-1, Encoder.getAlphanumericCode('\0'));
64 public void testChooseMode() throws WriterException {
66 assertSame(Mode.NUMERIC, Encoder.chooseMode("0"));
67 assertSame(Mode.NUMERIC, Encoder.chooseMode("0123456789"));
69 assertSame(Mode.ALPHANUMERIC, Encoder.chooseMode("A"));
70 assertSame(Mode.ALPHANUMERIC,
71 Encoder.chooseMode("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"));
73 assertSame(Mode.BYTE, Encoder.chooseMode("a"));
74 assertSame(Mode.BYTE, Encoder.chooseMode("#"));
75 assertSame(Mode.BYTE, Encoder.chooseMode(""));
76 // Kanji mode. We used to use MODE_KANJI for these, but we stopped
77 // doing that as we cannot distinguish Shift_JIS from other encodings
78 // from data bytes alone. See also comments in qrcode_encoder.h.
80 // AIUE in Hiragana in Shift_JIS
82 Encoder.chooseMode(shiftJISString(new byte[]{0x8, 0xa, 0x8, 0xa, 0x8, 0xa, 0x8, (byte) 0xa6})));
84 // Nihon in Kanji in Shift_JIS.
85 assertSame(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[]{0x9, 0xf, 0x9, 0x7b})));
87 // Sou-Utsu-Byou in Kanji in Shift_JIS.
88 assertSame(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[]{0xe, 0x4, 0x9, 0x5, 0x9, 0x61})));
92 public void testEncode() throws WriterException {
93 QRCode qrCode = new QRCode();
94 Encoder.encode("ABCDEF", ErrorCorrectionLevel.H, qrCode);
95 // The following is a valid QR Code that can be read by cell phones.
98 " mode: ALPHANUMERIC\n" +
101 " matrixWidth: 21\n" +
102 " maskPattern: 0\n" +
103 " numTotalBytes: 26\n" +
104 " numDataBytes: 9\n" +
105 " numECBytes: 17\n" +
106 " numRSBlocks: 1\n" +
108 " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1\n" +
109 " 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1\n" +
110 " 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 1 1 0 1\n" +
111 " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" +
112 " 1 0 1 1 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1\n" +
113 " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1\n" +
114 " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" +
115 " 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0\n" +
116 " 0 0 1 0 1 1 1 0 1 1 0 0 1 1 0 0 0 1 0 0 1\n" +
117 " 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0\n" +
118 " 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 1 0\n" +
119 " 1 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 0\n" +
120 " 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0\n" +
121 " 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0\n" +
122 " 1 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1\n" +
123 " 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1\n" +
124 " 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 0 1\n" +
125 " 1 0 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 1 0 1 0\n" +
126 " 1 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1\n" +
127 " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 1\n" +
128 " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1\n" +
130 assertEquals(expected, qrCode.toString());
134 public void testAppendModeInfo() {
135 BitArray bits = new BitArray();
136 Encoder.appendModeInfo(Mode.NUMERIC, bits);
137 assertEquals(" ...X", bits.toString());
141 public void testAppendLengthInfo() throws WriterException {
143 BitArray bits = new BitArray();
144 Encoder.appendLengthInfo(1, // 1 letter (1/1).
148 assertEquals(" ........ .X", bits.toString()); // 10 bits.
151 BitArray bits = new BitArray();
152 Encoder.appendLengthInfo(2, // 2 letters (2/1).
156 assertEquals(" ........ .X.", bits.toString()); // 11 bits.
159 BitArray bits = new BitArray();
160 Encoder.appendLengthInfo(255, // 255 letter (255/1).
164 assertEquals(" ........ XXXXXXXX", bits.toString()); // 16 bits.
167 BitArray bits = new BitArray();
168 Encoder.appendLengthInfo(512, // 512 letters (1024/2).
172 assertEquals(" ..X..... ....", bits.toString()); // 12 bits.
177 public void testAppendBytes() throws WriterException {
179 // Should use appendNumericBytes.
180 // 1 = 01 = 0001 in 4 bits.
181 BitArray bits = new BitArray();
182 Encoder.appendBytes("1", Mode.NUMERIC, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
183 assertEquals(" ...X" , bits.toString());
186 // Should use appendAlphanumericBytes.
187 // A = 10 = 0xa = 001010 in 6 bits
188 BitArray bits = new BitArray();
189 Encoder.appendBytes("A", Mode.ALPHANUMERIC, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
190 assertEquals(" ..X.X." , bits.toString());
191 // Lower letters such as 'a' cannot be encoded in MODE_ALPHANUMERIC.
193 Encoder.appendBytes("a", Mode.ALPHANUMERIC, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
194 } catch (WriterException we) {
199 // Should use append8BitBytes.
201 BitArray bits = new BitArray();
202 Encoder.appendBytes("abc", Mode.BYTE, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
203 assertEquals(" .XX....X .XX...X. .XX...XX", bits.toString());
204 // Anything can be encoded in QRCode.MODE_8BIT_BYTE.
205 Encoder.appendBytes("\0", Mode.BYTE, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
208 // Should use appendKanjiBytes.
210 BitArray bits = new BitArray();
211 Encoder.appendBytes(shiftJISString(new byte[] {(byte)0x93,0x5f}), Mode.KANJI, bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
212 assertEquals(" .XX.XX.. XXXXX", bits.toString());
217 public void testTerminateBits() throws WriterException {
219 BitArray v = new BitArray();
220 Encoder.terminateBits(0, v);
221 assertEquals("", v.toString());
224 BitArray v = new BitArray();
225 Encoder.terminateBits(1, v);
226 assertEquals(" ........", v.toString());
229 BitArray v = new BitArray();
230 v.appendBits(0, 3); // Append 000
231 Encoder.terminateBits(1, v);
232 assertEquals(" ........", v.toString());
235 BitArray v = new BitArray();
236 v.appendBits(0, 5); // Append 00000
237 Encoder.terminateBits(1, v);
238 assertEquals(" ........", v.toString());
241 BitArray v = new BitArray();
242 v.appendBits(0, 8); // Append 00000000
243 Encoder.terminateBits(1, v);
244 assertEquals(" ........", v.toString());
247 BitArray v = new BitArray();
248 Encoder.terminateBits(2, v);
249 assertEquals(" ........ XXX.XX..", v.toString());
252 BitArray v = new BitArray();
253 v.appendBits(0, 1); // Append 0
254 Encoder.terminateBits(3, v);
255 assertEquals(" ........ XXX.XX.. ...X...X", v.toString());
260 public void testGetNumDataBytesAndNumECBytesForBlockID() throws WriterException {
261 int[] numDataBytes = new int[1];
262 int[] numEcBytes = new int[1];
264 Encoder.getNumDataBytesAndNumECBytesForBlockID(26, 9, 1, 0, numDataBytes, numEcBytes);
265 assertEquals(9, numDataBytes[0]);
266 assertEquals(17, numEcBytes[0]);
268 // Version 3-H. 2 blocks.
269 Encoder.getNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 0, numDataBytes, numEcBytes);
270 assertEquals(13, numDataBytes[0]);
271 assertEquals(22, numEcBytes[0]);
272 Encoder.getNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 1, numDataBytes, numEcBytes);
273 assertEquals(13, numDataBytes[0]);
274 assertEquals(22, numEcBytes[0]);
276 // Version 7-H. (4 + 1) blocks.
277 Encoder.getNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 0, numDataBytes, numEcBytes);
278 assertEquals(13, numDataBytes[0]);
279 assertEquals(26, numEcBytes[0]);
280 Encoder.getNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 4, numDataBytes, numEcBytes);
281 assertEquals(14, numDataBytes[0]);
282 assertEquals(26, numEcBytes[0]);
284 // Version 40-H. (20 + 61) blocks.
285 Encoder.getNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 0, numDataBytes, numEcBytes);
286 assertEquals(15, numDataBytes[0]);
287 assertEquals(30, numEcBytes[0]);
288 Encoder.getNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 20, numDataBytes, numEcBytes);
289 assertEquals(16, numDataBytes[0]);
290 assertEquals(30, numEcBytes[0]);
291 Encoder.getNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 80, numDataBytes, numEcBytes);
292 assertEquals(16, numDataBytes[0]);
293 assertEquals(30, numEcBytes[0]);
297 public void testInterleaveWithECBytes() throws WriterException {
299 byte[] dataBytes = {32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236};
300 BitArray in = new BitArray();
301 for (byte dataByte: dataBytes) {
302 in.appendBits(dataByte, 8);
304 BitArray out = new BitArray();
305 Encoder.interleaveWithECBytes(in, 26, 9, 1, out);
308 32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236,
309 // Error correction bytes.
310 42, (byte)159, 74, (byte)221, (byte)244, (byte)169, (byte)239, (byte)150, (byte)138, 70,
311 (byte)237, 85, (byte)224, 96, 74, (byte)219, 61,
313 assertEquals(expected.length, out.getSizeInBytes());
314 byte[] outArray = new byte[expected.length];
315 out.toBytes(0, outArray, 0, expected.length);
316 // Can't use Arrays.equals(), because outArray may be longer than out.sizeInBytes()
317 for (int x = 0; x < expected.length; x++) {
318 assertEquals(expected[x], outArray[x]);
321 // Numbers are from http://www.swetake.com/qr/qr8.html
324 67, 70, 22, 38, 54, 70, 86, 102, 118, (byte)134, (byte)150, (byte)166, (byte)182,
325 (byte)198, (byte)214, (byte)230, (byte)247, 7, 23, 39, 55, 71, 87, 103, 119, (byte)135,
326 (byte)151, (byte)166, 22, 38, 54, 70, 86, 102, 118, (byte)134, (byte)150, (byte)166,
327 (byte)182, (byte)198, (byte)214, (byte)230, (byte)247, 7, 23, 39, 55, 71, 87, 103, 119,
328 (byte)135, (byte)151, (byte)160, (byte)236, 17, (byte)236, 17, (byte)236, 17, (byte)236,
331 BitArray in = new BitArray();
332 for (byte dataByte: dataBytes) {
333 in.appendBits(dataByte, 8);
335 BitArray out = new BitArray();
336 Encoder.interleaveWithECBytes(in, 134, 62, 4, out);
339 67, (byte)230, 54, 55, 70, (byte)247, 70, 71, 22, 7, 86, 87, 38, 23, 102, 103, 54, 39,
340 118, 119, 70, 55, (byte)134, (byte)135, 86, 71, (byte)150, (byte)151, 102, 87, (byte)166,
341 (byte)160, 118, 103, (byte)182, (byte)236, (byte)134, 119, (byte)198, 17, (byte)150,
342 (byte)135, (byte)214, (byte)236, (byte)166, (byte)151, (byte)230, 17, (byte)182,
343 (byte)166, (byte)247, (byte)236, (byte)198, 22, 7, 17, (byte)214, 38, 23, (byte)236, 39,
345 // Error correction bytes.
346 (byte)175, (byte)155, (byte)245, (byte)236, 80, (byte)146, 56, 74, (byte)155, (byte)165,
347 (byte)133, (byte)142, 64, (byte)183, (byte)132, 13, (byte)178, 54, (byte)132, 108, 45,
348 113, 53, 50, (byte)214, 98, (byte)193, (byte)152, (byte)233, (byte)147, 50, 71, 65,
349 (byte)190, 82, 51, (byte)209, (byte)199, (byte)171, 54, 12, 112, 57, 113, (byte)155, 117,
350 (byte)211, (byte)164, 117, 30, (byte)158, (byte)225, 31, (byte)190, (byte)242, 38,
351 (byte)140, 61, (byte)179, (byte)154, (byte)214, (byte)138, (byte)147, 87, 27, 96, 77, 47,
352 (byte)187, 49, (byte)156, (byte)214,
354 assertEquals(expected.length, out.getSizeInBytes());
355 byte[] outArray = new byte[expected.length];
356 out.toBytes(0, outArray, 0, expected.length);
357 for (int x = 0; x < expected.length; x++) {
358 assertEquals(expected[x], outArray[x]);
364 public void testAppendNumericBytes() {
366 // 1 = 01 = 0001 in 4 bits.
367 BitArray bits = new BitArray();
368 Encoder.appendNumericBytes("1", bits);
369 assertEquals(" ...X" , bits.toString());
372 // 12 = 0xc = 0001100 in 7 bits.
373 BitArray bits = new BitArray();
374 Encoder.appendNumericBytes("12", bits);
375 assertEquals(" ...XX.." , bits.toString());
378 // 123 = 0x7b = 0001111011 in 10 bits.
379 BitArray bits = new BitArray();
380 Encoder.appendNumericBytes("123", bits);
381 assertEquals(" ...XXXX. XX" , bits.toString());
384 // 1234 = "123" + "4" = 0001111011 + 0100
385 BitArray bits = new BitArray();
386 Encoder.appendNumericBytes("1234", bits);
387 assertEquals(" ...XXXX. XX.X.." , bits.toString());
391 BitArray bits = new BitArray();
392 Encoder.appendNumericBytes("", bits);
393 assertEquals("" , bits.toString());
398 public void testAppendAlphanumericBytes() throws WriterException {
400 // A = 10 = 0xa = 001010 in 6 bits
401 BitArray bits = new BitArray();
402 Encoder.appendAlphanumericBytes("A", bits);
403 assertEquals(" ..X.X." , bits.toString());
406 // AB = 10 * 45 + 11 = 461 = 0x1cd = 00111001101 in 11 bits
407 BitArray bits = new BitArray();
408 Encoder.appendAlphanumericBytes("AB", bits);
409 assertEquals(" ..XXX..X X.X", bits.toString());
412 // ABC = "AB" + "C" = 00111001101 + 001100
413 BitArray bits = new BitArray();
414 Encoder.appendAlphanumericBytes("ABC", bits);
415 assertEquals(" ..XXX..X X.X..XX. ." , bits.toString());
419 BitArray bits = new BitArray();
420 Encoder.appendAlphanumericBytes("", bits);
421 assertEquals("" , bits.toString());
425 BitArray bits = new BitArray();
427 Encoder.appendAlphanumericBytes("abc", bits);
428 } catch (WriterException we) {
435 public void testAppend8BitBytes() throws WriterException {
438 BitArray bits = new BitArray();
439 Encoder.append8BitBytes("abc", bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
440 assertEquals(" .XX....X .XX...X. .XX...XX", bits.toString());
444 BitArray bits = new BitArray();
445 Encoder.append8BitBytes("", bits, Encoder.DEFAULT_BYTE_MODE_ENCODING);
446 assertEquals("", bits.toString());
450 // Numbers are from page 21 of JISX0510:2004
452 public void testAppendKanjiBytes() throws WriterException {
453 BitArray bits = new BitArray();
454 Encoder.appendKanjiBytes(shiftJISString(new byte[] {(byte)0x93,0x5f}), bits);
455 assertEquals(" .XX.XX.. XXXXX", bits.toString());
456 Encoder.appendKanjiBytes(shiftJISString(new byte[] {(byte)0xe4,(byte)0xaa}), bits);
457 assertEquals(" .XX.XX.. XXXXXXX. X.X.X.X. X.", bits.toString());
460 // Numbers are from http://www.swetake.com/qr/qr3.html and
461 // http://www.swetake.com/qr/qr9.html
463 public void testGenerateECBytes() {
465 byte[] dataBytes = {32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236};
466 byte[] ecBytes = Encoder.generateECBytes(dataBytes, 17);
468 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61
470 assertEquals(expected.length, ecBytes.length);
471 for (int x = 0; x < expected.length; x++) {
472 assertEquals(expected[x], ecBytes[x] & 0xFF);
476 byte[] dataBytes = {67, 70, 22, 38, 54, 70, 86, 102, 118,
477 (byte)134, (byte)150, (byte)166, (byte)182, (byte)198, (byte)214};
478 byte[] ecBytes = Encoder.generateECBytes(dataBytes, 18);
480 175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187
482 assertEquals(expected.length, ecBytes.length);
483 for (int x = 0; x < expected.length; x++) {
484 assertEquals(expected[x], ecBytes[x] & 0xFF);
488 // High-order zero coefficient case.
489 byte[] dataBytes = {32, 49, (byte)205, 69, 42, 20, 0, (byte)236, 17};
490 byte[] ecBytes = Encoder.generateECBytes(dataBytes, 17);
492 0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213
494 assertEquals(expected.length, ecBytes.length);
495 for (int x = 0; x < expected.length; x++) {
496 assertEquals(expected[x], ecBytes[x] & 0xFF);
502 public void testBugInBitVectorNumBytes() throws WriterException {
503 // There was a bug in BitVector.sizeInBytes() that caused it to return a
504 // smaller-by-one value (ex. 1465 instead of 1466) if the number of bits
505 // in the vector is not 8-bit aligned. In QRCodeEncoder::InitQRCode(),
506 // BitVector::sizeInBytes() is used for finding the smallest QR Code
507 // version that can fit the given data. Hence there were corner cases
508 // where we chose a wrong QR Code version that cannot fit the given
509 // data. Note that the issue did not occur with MODE_8BIT_BYTE, as the
510 // bits in the bit vector are always 8-bit aligned.
512 // Before the bug was fixed, the following test didn't pass, because:
514 // - MODE_NUMERIC is chosen as all bytes in the data are '0'
515 // - The 3518-byte numeric data needs 1466 bytes
516 // - 3518 / 3 * 10 + 7 = 11727 bits = 1465.875 bytes
517 // - 3 numeric bytes are encoded in 10 bits, hence the first
518 // 3516 bytes are encoded in 3516 / 3 * 10 = 11720 bits.
519 // - 2 numeric bytes can be encoded in 7 bits, hence the last
520 // 2 bytes are encoded in 7 bits.
521 // - The version 27 QR Code with the EC level L has 1468 bytes for data.
522 // - 1828 - 360 = 1468
523 // - In InitQRCode(), 3 bytes are reserved for a header. Hence 1465 bytes
524 // (1468 -3) are left for data.
525 // - Because of the bug in BitVector::sizeInBytes(), InitQRCode() determines
526 // the given data can fit in 1465 bytes, despite it needs 1466 bytes.
527 // - Hence QRCodeEncoder.encode() failed and returned false.
528 // - To be precise, it needs 11727 + 4 (getMode info) + 14 (length info) =
529 // 11745 bits = 1468.125 bytes are needed (i.e. cannot fit in 1468
531 StringBuilder builder = new StringBuilder(3518);
532 for (int x = 0; x < 3518; x++) {
535 QRCode qrCode = new QRCode();
536 Encoder.encode(builder.toString(), ErrorCorrectionLevel.L, qrCode);
539 private static String shiftJISString(byte[] bytes) throws WriterException {
541 return new String(bytes, "Shift_JIS");
542 } catch (UnsupportedEncodingException uee) {
543 throw new WriterException(uee.toString());