xref: /aosp_15_r20/external/zxing/core/src/test/java/com/google/zxing/aztec/encoder/EncoderTest.java (revision 513427e33d61bc67fc40bc261642ac0b2a686b45)
1 /*
2  * Copyright 2013 ZXing authors
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.google.zxing.aztec.encoder;
18 
19 import com.google.zxing.BarcodeFormat;
20 import com.google.zxing.EncodeHintType;
21 import com.google.zxing.FormatException;
22 import com.google.zxing.ResultPoint;
23 import com.google.zxing.aztec.AztecDetectorResult;
24 import com.google.zxing.aztec.AztecWriter;
25 import com.google.zxing.aztec.decoder.Decoder;
26 import com.google.zxing.common.BitArray;
27 import com.google.zxing.common.BitMatrix;
28 import com.google.zxing.common.DecoderResult;
29 import org.junit.Assert;
30 import org.junit.Test;
31 
32 import java.nio.charset.Charset;
33 import java.nio.charset.StandardCharsets;
34 import java.util.EnumMap;
35 import java.util.Map;
36 import java.util.Random;
37 import java.util.regex.Pattern;
38 
39 /**
40  * Aztec 2D generator unit tests.
41  *
42  * @author Rustam Abdullaev
43  * @author Frank Yellin
44  */
45 public final class EncoderTest extends Assert {
46 
47   private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
48   private static final Charset UTF_8 = StandardCharsets.UTF_8;
49   private static final Charset SHIFT_JIS = Charset.forName("Shift_JIS");
50   private static final Charset ISO_8859_15 = Charset.forName("ISO-8859-15");
51   private static final Charset WINDOWS_1252 = Charset.forName("Windows-1252");
52 
53   private static final Pattern DOTX = Pattern.compile("[^.X]");
54   private static final Pattern SPACES = Pattern.compile("\\s+");
55   private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
56 
57   // real life tests
58 
59   @Test
testEncode1()60   public void testEncode1() {
61     testEncode("This is an example Aztec symbol for Wikipedia.", true, 3,
62         "X     X X       X     X X     X     X         \n" +
63         "X         X     X X     X   X X   X X       X \n" +
64         "X X   X X X X X   X X X                 X     \n" +
65         "X X                 X X   X       X X X X X X \n" +
66         "    X X X   X   X     X X X X         X X     \n" +
67         "  X X X   X X X X   X     X   X     X X   X   \n" +
68         "        X X X X X     X X X X   X   X     X   \n" +
69         "X       X   X X X X X X X X X X X     X   X X \n" +
70         "X   X     X X X               X X X X   X X   \n" +
71         "X     X X   X X   X X X X X   X X   X   X X X \n" +
72         "X   X         X   X       X   X X X X       X \n" +
73         "X       X     X   X   X   X   X   X X   X     \n" +
74         "      X   X X X   X       X   X     X X X     \n" +
75         "    X X X X X X   X X X X X   X X X X X X   X \n" +
76         "  X X   X   X X               X X X   X X X X \n" +
77         "  X   X       X X X X X X X X X X X X   X X   \n" +
78         "  X X   X       X X X   X X X       X X       \n" +
79         "  X               X   X X     X     X X X     \n" +
80         "  X   X X X   X X   X   X X X X   X   X X X X \n" +
81         "    X   X   X X X   X   X   X X X X     X     \n" +
82         "        X               X                 X   \n" +
83         "        X X     X   X X   X   X   X       X X \n" +
84         "  X   X   X X       X   X         X X X     X \n");
85   }
86 
87   @Test
testEncode2()88   public void testEncode2() {
89     testEncode("Aztec Code is a public domain 2D matrix barcode symbology" +
90                 " of nominally square symbols built on a square grid with a " +
91                 "distinctive square bullseye pattern at their center.", false, 6,
92           "        X X     X X     X     X     X   X X X         X   X         X   X X       \n" +
93           "  X       X X     X   X X   X X       X             X     X   X X   X           X \n" +
94           "  X   X X X     X   X   X X     X X X   X   X X               X X       X X     X \n" +
95           "X X X             X   X         X         X     X     X   X     X X       X   X   \n" +
96           "X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X \n" +
97           "    X X   X   X   X X X               X       X       X X     X X   X X       X   \n" +
98           "X X     X       X       X X X X   X   X X       X   X X   X       X X   X X   X   \n" +
99           "  X       X   X     X X   X   X X   X X   X X X X X X   X X           X   X   X X \n" +
100           "X X   X X   X   X X X X   X X X X X X X X   X   X       X X   X X X X   X X X     \n" +
101           "  X       X   X     X       X X     X X   X   X   X     X X   X X X   X     X X X \n" +
102           "  X   X X X   X X       X X X         X X           X   X   X   X X X   X X     X \n" +
103           "    X     X   X X     X X X X     X   X     X X X X   X X   X X   X X X     X   X \n" +
104           "X X X   X             X         X X X X X   X   X X   X   X   X X   X   X   X   X \n" +
105           "          X       X X X   X X     X   X           X   X X X X   X X               \n" +
106           "  X     X X   X   X       X X X X X X X X X X X X X X X   X   X X   X   X X X     \n" +
107           "    X X                 X   X                       X X   X       X         X X X \n" +
108           "        X   X X   X X X X X X   X X X X X X X X X   X     X X           X X X X   \n" +
109           "          X X X   X     X   X   X               X   X X     X X X   X X           \n" +
110           "X X     X     X   X   X   X X   X   X X X X X   X   X X X X X X X       X   X X X \n" +
111           "X X X X       X       X   X X   X   X       X   X   X     X X X     X X       X X \n" +
112           "X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X \n" +
113           "    X     X       X         X   X   X       X   X   X     X   X X                 \n" +
114           "        X X     X X X X X   X   X   X X X X X   X   X X X     X X X X   X         \n" +
115           "X     X   X   X         X   X   X               X   X X   X X   X X X     X   X   \n" +
116           "  X   X X X   X   X X   X X X   X X X X X X X X X   X X         X X     X X X X   \n" +
117           "    X X   X   X   X X X     X                       X X X   X X   X   X     X     \n" +
118           "    X X X X   X         X   X X X X X X X X X X X X X X   X       X X   X X   X X \n" +
119           "            X   X   X X       X X X X X     X X X       X       X X X         X   \n" +
120           "X       X         X   X X X X   X     X X     X X     X X           X   X       X \n" +
121           "X     X       X X X X X     X   X X X X   X X X     X       X X X X   X   X X   X \n" +
122           "  X X X X X               X     X X X   X       X X   X X   X X X X     X X       \n" +
123           "X             X         X   X X   X X     X     X     X   X   X X X X             \n" +
124           "    X   X X       X     X       X   X X X X X X   X X   X X X X X X X X X   X   X \n" +
125           "    X         X X   X       X     X   X   X       X     X X X     X       X X X X \n" +
126           "X     X X     X X X X X X             X X X   X               X   X     X     X X \n" +
127           "X   X X     X               X X X X X     X X     X X X X X X X X     X   X   X X \n" +
128           "X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X   X \n" +
129           "X           X     X X X X     X     X         X         X   X       X X   X X X   \n" +
130           "X   X   X X   X X X   X         X X     X X X X     X X   X   X     X   X       X \n" +
131           "      X     X     X     X X     X   X X   X X   X         X X       X       X   X \n" +
132           "X       X           X   X   X     X X   X               X     X     X X X         \n");
133   }
134 
135   @Test
testAztecWriter()136   public void testAztecWriter() throws Exception {
137     testWriter("Espa\u00F1ol", null, 25, true, 1);                   // Without ECI (implicit ISO-8859-1)
138     testWriter("Espa\u00F1ol", ISO_8859_1, 25, true, 1);             // Explicit ISO-8859-1
139     testWriter("\u20AC 1 sample data.", WINDOWS_1252, 25, true, 2);  // ISO-8859-1 can't encode Euro; Windows-1252 can
140     testWriter("\u20AC 1 sample data.", ISO_8859_15, 25, true, 2);
141     testWriter("\u20AC 1 sample data.", UTF_8, 25, true, 2);
142     testWriter("\u20AC 1 sample data.", UTF_8, 100, true, 3);
143     testWriter("\u20AC 1 sample data.", UTF_8, 300, true, 4);
144     testWriter("\u20AC 1 sample data.", UTF_8, 500, false, 5);
145     testWriter("The capital of Japan is named \u6771\u4EAC.", SHIFT_JIS, 25, true, 3);
146     // Test AztecWriter defaults
147     String data = "In ut magna vel mauris malesuada";
148     AztecWriter writer = new AztecWriter();
149     BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0);
150     AztecCode aztec = Encoder.encode(data,
151         Encoder.DEFAULT_EC_PERCENT, Encoder.DEFAULT_AZTEC_LAYERS);
152     BitMatrix expectedMatrix = aztec.getMatrix();
153     assertEquals(matrix, expectedMatrix);
154   }
155 
156   // synthetic tests (encode-decode round-trip)
157 
158   @Test
testEncodeDecode1()159   public void testEncodeDecode1() throws Exception {
160     testEncodeDecode("Abc123!", true, 1);
161   }
162 
163   @Test
testEncodeDecode2()164   public void testEncodeDecode2() throws Exception {
165     testEncodeDecode("Lorem ipsum. http://test/", true, 2);
166   }
167 
168   @Test
testEncodeDecode3()169   public void testEncodeDecode3() throws Exception {
170     testEncodeDecode("AAAANAAAANAAAANAAAANAAAANAAAANAAAANAAAANAAAANAAAAN", true, 3);
171   }
172 
173   @Test
testEncodeDecode4()174   public void testEncodeDecode4() throws Exception {
175     testEncodeDecode("http://test/~!@#*^%&)__ ;:'\"[]{}\\|-+-=`1029384", true, 4);
176   }
177 
178   @Test
testEncodeDecode5()179   public void testEncodeDecode5() throws Exception {
180     testEncodeDecode("http://test/~!@#*^%&)__ ;:'\"[]{}\\|-+-=`1029384756<>/?abc"
181         + "Four score and seven our forefathers brought forth", false, 5);
182   }
183 
184   @Test
testEncodeDecode10()185   public void testEncodeDecode10() throws Exception {
186     testEncodeDecode("In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam" +
187         " cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum" +
188         " est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue" +
189         " auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla" +
190         " ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id" +
191         " elementum sapien dolor et diam.", false, 10);
192   }
193 
194   @Test
testEncodeDecode23()195   public void testEncodeDecode23() throws Exception {
196     testEncodeDecode("In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam" +
197         " cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum" +
198         " est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue" +
199         " auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla" +
200         " ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id" +
201         " elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend." +
202         " Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus" +
203         " justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu" +
204         " tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus" +
205         " quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec" +
206         " laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida," +
207         " justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec" +
208         " lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar" +
209         " nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat" +
210         " eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra" +
211         " fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo" +
212         " diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla" +
213         " ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum" +
214         " sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet." +
215         " Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit" +
216         " felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo" +
217         " erat pulvinar nisi, id elementum sapien dolor et diam.", false, 23);
218   }
219 
220   @Test
testEncodeDecode31()221   public void testEncodeDecode31() throws Exception {
222     testEncodeDecode("In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam" +
223         " cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum" +
224         " est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue" +
225         " auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla" +
226         " ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id" +
227         " elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend." +
228         " Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus" +
229         " justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu" +
230         " tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus" +
231         " quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec" +
232         " laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida," +
233         " justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec" +
234         " lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar" +
235         " nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat" +
236         " eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra" +
237         " fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo" +
238         " diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla" +
239         " ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum" +
240         " sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet." +
241         " Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit" +
242         " felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo" +
243         " erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit" +
244         " placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at" +
245         " pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est." +
246         " Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada" +
247         " dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id" +
248         " justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum" +
249         " sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat," +
250         " eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet" +
251         " laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac" +
252         " nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula," +
253         " massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus" +
254         " sed est. Ut justo diam, lobortis eu tris. In ut magna vel mauris malesuada dictum." +
255         " Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum" +
256         " sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet." +
257         " Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget" +
258         " hendrerit felis turpis nec lorem.", false, 31);
259   }
260 
261   @Test
testGenerateModeMessage()262   public void testGenerateModeMessage() {
263     testModeMessage(true, 2, 29, ".X .XXX.. ...X XX.. ..X .XX. .XX.X");
264     testModeMessage(true, 4, 64, "XX XXXXXX .X.. ...X ..XX .X.. XX..");
265     testModeMessage(false, 21, 660,  "X.X.. .X.X..X..XX .XXX ..X.. .XXX. .X... ..XXX");
266     testModeMessage(false, 32, 4096, "XXXXX XXXXXXXXXXX X.X. ..... XXX.X ..X.. X.XXX");
267   }
268 
269   @Test
testStuffBits()270   public void testStuffBits() {
271     testStuffBits(5, ".X.X. X.X.X .X.X.",
272         ".X.X. X.X.X .X.X.");
273     testStuffBits(5, ".X.X. ..... .X.X",
274         ".X.X. ....X ..X.X");
275     testStuffBits(3, "XX. ... ... ..X XXX .X. ..",
276         "XX. ..X ..X ..X ..X .XX XX. .X. ..X");
277     testStuffBits(6, ".X.X.. ...... ..X.XX",
278         ".X.X.. .....X. ..X.XX XXXX.");
279     testStuffBits(6, ".X.X.. ...... ...... ..X.X.",
280         ".X.X.. .....X .....X ....X. X.XXXX");
281     testStuffBits(6, ".X.X.. XXXXXX ...... ..X.XX",
282         ".X.X.. XXXXX. X..... ...X.X XXXXX.");
283     testStuffBits(6,
284         "...... ..XXXX X..XX. .X.... .X.X.X .....X .X.... ...X.X .....X ....XX ..X... ....X. X..XXX X.XX.X",
285         ".....X ...XXX XX..XX ..X... ..X.X. X..... X.X... ....X. X..... X....X X..X.. .....X X.X..X XXX.XX .XXXXX");
286   }
287 
288   @Test
testHighLevelEncode()289   public void testHighLevelEncode() throws FormatException {
290     testHighLevelEncodeString("A. b.",
291         // 'A'  P/S   '. ' L/L    b    D/L    '.'
292         "...X. ..... ...XX XXX.. ...XX XXXX. XX.X");
293     testHighLevelEncodeString("Lorem ipsum.",
294         // 'L'  L/L   'o'   'r'   'e'   'm'   ' '   'i'   'p'   's'   'u'   'm'   D/L   '.'
295         ".XX.X XXX.. X.... X..XX ..XX. .XXX. ....X .X.X. X...X X.X.. X.XX. .XXX. XXXX. XX.X");
296     testHighLevelEncodeString("Lo. Test 123.",
297         // 'L'  L/L   'o'   P/S   '. '  U/S   'T'   'e'   's'   't'    D/L   ' '  '1'  '2'  '3'  '.'
298         ".XX.X XXX.. X.... ..... ...XX XXX.. X.X.X ..XX. X.X.. X.X.X  XXXX. ...X ..XX .X.. .X.X XX.X");
299     testHighLevelEncodeString("Lo...x",
300         // 'L'  L/L   'o'   D/L   '.'  '.'  '.'  U/L  L/L   'x'
301         ".XX.X XXX.. X.... XXXX. XX.X XX.X XX.X XXX. XXX.. XX..X");
302     testHighLevelEncodeString(". x://abc/.",
303         //P/S   '. '  L/L   'x'   P/S   ':'   P/S   '/'   P/S   '/'   'a'   'b'   'c'   P/S   '/'   D/L   '.'
304         "..... ...XX XXX.. XX..X ..... X.X.X ..... X.X.. ..... X.X.. ...X. ...XX ..X.. ..... X.X.. XXXX. XX.X");
305     // Uses Binary/Shift rather than Lower/Shift to save two bits.
306     testHighLevelEncodeString("ABCdEFG",
307         //'A'   'B'   'C'   B/S    =1    'd'     'E'   'F'   'G'
308         "...X. ...XX ..X.. XXXXX ....X .XX..X.. ..XX. ..XXX .X...");
309 
310     testHighLevelEncodeString(
311         // Found on an airline boarding pass.  Several stretches of Binary shift are
312         // necessary to keep the bitcount so low.
313         "09  UAG    ^160MEUCIQC0sYS/HpKxnBELR1uB85R20OoqqwFGa0q2uEi"
314             + "Ygh6utAIgLl1aBVM4EOTQtMQQYH9M2Z3Dp4qnA/fwWuQ+M8L3V8U=",
315         823);
316   }
317 
318   @Test
testHighLevelEncodeBinary()319   public void testHighLevelEncodeBinary() throws FormatException {
320     // binary short form single byte
321     testHighLevelEncodeString("N\0N",
322         // 'N'  B/S    =1   '\0'      N
323         ".XXXX XXXXX ....X ........ .XXXX");   // Encode "N" in UPPER
324 
325     testHighLevelEncodeString("N\0n",
326         // 'N'  B/S    =2   '\0'       'n'
327         ".XXXX XXXXX ...X. ........ .XX.XXX.");   // Encode "n" in BINARY
328 
329     // binary short form consecutive bytes
330     testHighLevelEncodeString("N\0\u0080 A",
331         // 'N'  B/S    =2    '\0'    \u0080   ' '  'A'
332         ".XXXX XXXXX ...X. ........ X....... ....X ...X.");
333 
334     // binary skipping over single character
335     testHighLevelEncodeString("\0a\u00FF\u0080 A",
336         // B/S  =4    '\0'      'a'     '\3ff'   '\200'   ' '   'A'
337         "XXXXX ..X.. ........ .XX....X XXXXXXXX X....... ....X ...X.");
338 
339     // getting into binary mode from digit mode
340     testHighLevelEncodeString("1234\0",
341         //D/L   '1'  '2'  '3'  '4'  U/L  B/S    =1    \0
342         "XXXX. ..XX .X.. .X.X .XX. XXX. XXXXX ....X ........"
343     );
344 
345     // Create a string in which every character requires binary
346     StringBuilder sb = new StringBuilder();
347     for (int i = 0; i <= 3000; i++) {
348       sb.append((char) (128 + (i % 30)));
349     }
350     // Test the output generated by Binary/Switch, particularly near the
351     // places where the encoding changes: 31, 62, and 2047+31=2078
352     for (int i : new int[] { 1, 2, 3, 10, 29, 30, 31, 32, 33,
353                              60, 61, 62, 63, 64, 2076, 2077, 2078, 2079, 2080, 2100 }) {
354       // This is the expected length of a binary string of length "i"
355       int expectedLength = (8 * i) +
356           ((i <= 31) ? 10 : (i <= 62) ? 20 : (i <= 2078) ? 21 : 31);
357       // Verify that we are correct about the length.
358       testHighLevelEncodeString(sb.substring(0, i), expectedLength);
359       if (i != 1 && i != 32 && i != 2079) {
360         // The addition of an 'a' at the beginning or end gets merged into the binary code
361         // in those cases where adding another binary character only adds 8 or 9 bits to the result.
362         // So we exclude the border cases i=1,32,2079
363         // A lower case letter at the beginning will be merged into binary mode
364         testHighLevelEncodeString('a' + sb.substring(0, i - 1), expectedLength);
365         // A lower case letter at the end will also be merged into binary mode
366         testHighLevelEncodeString(sb.substring(0, i - 1) + 'a', expectedLength);
367       }
368       // A lower case letter at both ends will enough to latch us into LOWER.
369       testHighLevelEncodeString('a' + sb.substring(0, i) + 'b', expectedLength + 15);
370     }
371 
372     sb = new StringBuilder();
373     for (int i = 0; i < 32; i++) {
374       sb.append('§'); // § forces binary encoding
375     }
376     sb.setCharAt(1, 'A');
377     // expect B/S(1) A B/S(30)
378     testHighLevelEncodeString(sb.toString(), 5 + 20 + 31 * 8);
379 
380     sb = new StringBuilder();
381     for (int i = 0; i < 31; i++) {
382       sb.append('§');
383     }
384     sb.setCharAt(1, 'A');
385     // expect B/S(31)
386     testHighLevelEncodeString(sb.toString(), 10 + 31 * 8);
387 
388     sb = new StringBuilder();
389     for (int i = 0; i < 34; i++) {
390       sb.append('§');
391     }
392     sb.setCharAt(1, 'A');
393     // expect B/S(31) B/S(3)
394     testHighLevelEncodeString(sb.toString(), 20 + 34 * 8);
395 
396     sb = new StringBuilder();
397     for (int i = 0; i < 64; i++) {
398       sb.append('§');
399     }
400     sb.setCharAt(30, 'A');
401     // expect B/S(64)
402     testHighLevelEncodeString(sb.toString(), 21 + 64 * 8);
403   }
404 
405   @Test
testHighLevelEncodePairs()406   public void testHighLevelEncodePairs() throws FormatException {
407     // Typical usage
408     testHighLevelEncodeString("ABC. DEF\r\n",
409         //  A     B    C    P/S   .<sp>   D    E     F    P/S   \r\n
410         "...X. ...XX ..X.. ..... ...XX ..X.X ..XX. ..XXX ..... ...X.");
411 
412     // We should latch to PUNCT mode, rather than shift.  Also check all pairs
413     testHighLevelEncodeString("A. : , \r\n",
414         // 'A'    M/L   P/L   ". "  ": "   ", " "\r\n"
415         "...X. XXX.X XXXX. ...XX ..X.X  ..X.. ...X.");
416 
417     // Latch to DIGIT rather than shift to PUNCT
418     testHighLevelEncodeString("A. 1234",
419         // 'A'  D/L   '.'  ' '  '1' '2'   '3'  '4'
420         "...X. XXXX. XX.X ...X ..XX .X.. .X.X .X X.");
421     // Don't bother leaving Binary Shift.
422     testHighLevelEncodeString("A\200. \200",
423         // 'A'  B/S    =2    \200      "."     " "     \200
424         "...X. XXXXX ..X.. X....... ..X.XXX. ..X..... X.......");
425   }
426 
427   @Test(expected = IllegalArgumentException.class)
testUserSpecifiedLayers()428   public void testUserSpecifiedLayers() {
429     doTestUserSpecifiedLayers(33);
430   }
431 
432   @Test(expected = IllegalArgumentException.class)
testUserSpecifiedLayers2()433   public void testUserSpecifiedLayers2() {
434     doTestUserSpecifiedLayers(-1);
435   }
436 
doTestUserSpecifiedLayers(int userSpecifiedLayers)437   private void doTestUserSpecifiedLayers(int userSpecifiedLayers) {
438     String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
439     AztecCode aztec = Encoder.encode(alphabet, 25, -2);
440     assertEquals(2, aztec.getLayers());
441     assertTrue(aztec.isCompact());
442 
443     aztec = Encoder.encode(alphabet, 25, 32);
444     assertEquals(32, aztec.getLayers());
445     assertFalse(aztec.isCompact());
446 
447     Encoder.encode(alphabet, 25, userSpecifiedLayers);
448   }
449 
450   @Test(expected = IllegalArgumentException.class)
testBorderCompact4CaseFailed()451   public void testBorderCompact4CaseFailed() {
452     // Compact(4) con hold 608 bits of information, but at most 504 can be data.  Rest must
453     // be error correction
454     String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
455     // encodes as 26 * 5 * 4 = 520 bits of data
456     String alphabet4 = alphabet + alphabet + alphabet + alphabet;
457     Encoder.encode(alphabet4, 0, -4);
458   }
459 
460   @Test
testBorderCompact4Case()461   public void testBorderCompact4Case() {
462     // Compact(4) con hold 608 bits of information, but at most 504 can be data.  Rest must
463     // be error correction
464     String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
465     // encodes as 26 * 5 * 4 = 520 bits of data
466     String alphabet4 = alphabet + alphabet + alphabet + alphabet;
467 
468     // If we just try to encode it normally, it will go to a non-compact 4 layer
469     AztecCode aztecCode = Encoder.encode(alphabet4, 0, Encoder.DEFAULT_AZTEC_LAYERS);
470     assertFalse(aztecCode.isCompact());
471     assertEquals(4, aztecCode.getLayers());
472 
473     // But shortening the string to 100 bytes (500 bits of data), compact works fine, even if we
474     // include more error checking.
475     aztecCode = Encoder.encode(alphabet4.substring(0, 100), 10, Encoder.DEFAULT_AZTEC_LAYERS);
476     assertTrue(aztecCode.isCompact());
477     assertEquals(4, aztecCode.getLayers());
478   }
479 
480   // Helper routines
481 
testEncode(String data, boolean compact, int layers, String expected)482   private static void testEncode(String data, boolean compact, int layers, String expected) {
483     AztecCode aztec = Encoder.encode(data, 33, Encoder.DEFAULT_AZTEC_LAYERS);
484     assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
485     assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
486     BitMatrix matrix = aztec.getMatrix();
487     assertEquals("encode() failed", expected, matrix.toString());
488   }
489 
testEncodeDecode(String data, boolean compact, int layers)490   private static void testEncodeDecode(String data, boolean compact, int layers) throws Exception {
491     AztecCode aztec = Encoder.encode(data, 25, Encoder.DEFAULT_AZTEC_LAYERS);
492     assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
493     assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
494     BitMatrix matrix = aztec.getMatrix();
495     AztecDetectorResult r =
496         new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
497     DecoderResult res = new Decoder().decode(r);
498     assertEquals(data, res.getText());
499     // Check error correction by introducing a few minor errors
500     Random random = getPseudoRandom();
501     matrix.flip(random.nextInt(matrix.getWidth()), random.nextInt(2));
502     matrix.flip(random.nextInt(matrix.getWidth()), matrix.getHeight() - 2 + random.nextInt(2));
503     matrix.flip(random.nextInt(2), random.nextInt(matrix.getHeight()));
504     matrix.flip(matrix.getWidth() - 2 + random.nextInt(2), random.nextInt(matrix.getHeight()));
505     r = new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
506     res = new Decoder().decode(r);
507     assertEquals(data, res.getText());
508   }
509 
testWriter(String data, Charset charset, int eccPercent, boolean compact, int layers)510   private static void testWriter(String data,
511                                  Charset charset,
512                                  int eccPercent,
513                                  boolean compact,
514                                  int layers) throws FormatException {
515     // Perform an encode-decode round-trip because it can be lossy.
516     Map<EncodeHintType,Object> hints = new EnumMap<>(EncodeHintType.class);
517     if (null != charset) {
518       hints.put(EncodeHintType.CHARACTER_SET, charset.name());
519     }
520     hints.put(EncodeHintType.ERROR_CORRECTION, eccPercent);
521     AztecWriter writer = new AztecWriter();
522     BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0, hints);
523     AztecCode aztec = Encoder.encode(data, eccPercent,
524         Encoder.DEFAULT_AZTEC_LAYERS, charset);
525     assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
526     assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
527     BitMatrix matrix2 = aztec.getMatrix();
528     assertEquals(matrix, matrix2);
529     AztecDetectorResult r =
530         new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
531     DecoderResult res = new Decoder().decode(r);
532     assertEquals(data, res.getText());
533     // Check error correction by introducing up to eccPercent/2 errors
534     int ecWords = aztec.getCodeWords() * eccPercent / 100 / 2;
535     Random random = getPseudoRandom();
536     for (int i = 0; i < ecWords; i++) {
537       // don't touch the core
538       int x = random.nextBoolean() ?
539                 random.nextInt(aztec.getLayers() * 2)
540                 : matrix.getWidth() - 1 - random.nextInt(aztec.getLayers() * 2);
541       int y = random.nextBoolean() ?
542                 random.nextInt(aztec.getLayers() * 2)
543                 : matrix.getHeight() - 1 - random.nextInt(aztec.getLayers() * 2);
544       matrix.flip(x, y);
545     }
546     r = new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers(), 0);
547     res = new Decoder().decode(r);
548     assertEquals(data, res.getText());
549   }
550 
getPseudoRandom()551   private static Random getPseudoRandom() {
552     return new Random(0xDEADBEEF);
553   }
554 
testModeMessage(boolean compact, int layers, int words, String expected)555   private static void testModeMessage(boolean compact, int layers, int words, String expected) {
556     BitArray in = Encoder.generateModeMessage(compact, layers, words);
557     assertEquals("generateModeMessage() failed", stripSpace(expected), stripSpace(in.toString()));
558   }
559 
testStuffBits(int wordSize, String bits, String expected)560   private static void testStuffBits(int wordSize, String bits, String expected) {
561     BitArray in = toBitArray(bits);
562     BitArray stuffed = Encoder.stuffBits(in, wordSize);
563     assertEquals("stuffBits() failed for input string: " + bits,
564                  stripSpace(expected), stripSpace(stuffed.toString()));
565   }
566 
toBitArray(CharSequence bits)567   public static BitArray toBitArray(CharSequence bits) {
568     BitArray in = new BitArray();
569     char[] str = DOTX.matcher(bits).replaceAll("").toCharArray();
570     for (char aStr : str) {
571       in.appendBit(aStr == 'X');
572     }
573     return in;
574   }
575 
toBooleanArray(BitArray bitArray)576   public static boolean[] toBooleanArray(BitArray bitArray) {
577     boolean[] result = new boolean[bitArray.getSize()];
578     for (int i = 0; i < result.length; i++) {
579       result[i] = bitArray.get(i);
580     }
581     return result;
582   }
583 
testHighLevelEncodeString(String s, String expectedBits)584   private static void testHighLevelEncodeString(String s, String expectedBits) throws FormatException {
585     BitArray bits = new HighLevelEncoder(s.getBytes(StandardCharsets.ISO_8859_1)).encode();
586     String receivedBits = stripSpace(bits.toString());
587     assertEquals("highLevelEncode() failed for input string: " + s, stripSpace(expectedBits), receivedBits);
588     assertEquals(s, Decoder.highLevelDecode(toBooleanArray(bits)));
589   }
590 
testHighLevelEncodeString(String s, int expectedReceivedBits)591   private static void testHighLevelEncodeString(String s, int expectedReceivedBits) throws FormatException {
592     BitArray bits = new HighLevelEncoder(s.getBytes(StandardCharsets.ISO_8859_1)).encode();
593     int receivedBitCount = stripSpace(bits.toString()).length();
594     assertEquals("highLevelEncode() failed for input string: " + s,
595                  expectedReceivedBits, receivedBitCount);
596     assertEquals(s, Decoder.highLevelDecode(toBooleanArray(bits)));
597   }
598 
stripSpace(String s)599   public static String stripSpace(String s) {
600     return SPACES.matcher(s).replaceAll("");
601   }
602 
603 }
604