1 /*
2 * Copyright 2016 Huy Cuong Nguyen
3 * Copyright 2016 ZXing authors
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 
18 #include "AZEncoder.h"
19 
20 #include "AZHighLevelEncoder.h"
21 #include "BitArray.h"
22 #include "GenericGF.h"
23 #include "ReedSolomonEncoder.h"
24 #include "ZXTestSupport.h"
25 
26 #include <cstdlib>
27 #include <stdexcept>
28 #include <vector>
29 
30 namespace ZXing::Aztec {
31 
32 static const int MAX_NB_BITS = 32;
33 static const int MAX_NB_BITS_COMPACT = 4;
34 
35 static const int WORD_SIZE[] = {4,  6,  6,  8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10,
36 								10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12};
37 
DrawBullsEye(BitMatrix & matrix,int center,int size)38 static void DrawBullsEye(BitMatrix& matrix, int center, int size)
39 {
40 	for (int i = 0; i < size; i += 2) {
41 		for (int j = center - i; j <= center + i; j++) {
42 			matrix.set(j, center - i);
43 			matrix.set(j, center + i);
44 			matrix.set(center - i, j);
45 			matrix.set(center + i, j);
46 		}
47 	}
48 	matrix.set(center - size, center - size);
49 	matrix.set(center - size + 1, center - size);
50 	matrix.set(center - size, center - size + 1);
51 	matrix.set(center + size, center - size);
52 	matrix.set(center + size, center - size + 1);
53 	matrix.set(center + size, center + size - 1);
54 }
55 
GetGFFromWordSize(int wordSize)56 static const GenericGF& GetGFFromWordSize(int wordSize)
57 {
58 	switch (wordSize) {
59 	case 4:  return GenericGF::AztecParam();
60 	case 6:  return GenericGF::AztecData6();
61 	case 8:  return GenericGF::AztecData8();
62 	case 10: return GenericGF::AztecData10();
63 	case 12: return GenericGF::AztecData12();
64 	default: throw std::invalid_argument("Unsupported word size " + std::to_string(wordSize));
65 	}
66 }
67 
GenerateCheckWords(const BitArray & bitArray,int totalBits,int wordSize,BitArray & messageBits)68 static void GenerateCheckWords(const BitArray& bitArray, int totalBits, int wordSize, BitArray& messageBits)
69 {
70 	// bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
71 	std::vector<int> messageWords = ToInts(bitArray, wordSize, totalBits / wordSize);
72 	ReedSolomonEncode(GetGFFromWordSize(wordSize), messageWords, (totalBits - bitArray.size()) / wordSize);
73 	int startPad = totalBits % wordSize;
74 	messageBits = BitArray();
75 	messageBits.appendBits(0, startPad);
76 	for (int messageWord : messageWords)
77 		messageBits.appendBits(messageWord, wordSize);
78 }
79 
80 ZXING_EXPORT_TEST_ONLY
GenerateModeMessage(bool compact,int layers,int messageSizeInWords,BitArray & modeMessage)81 void GenerateModeMessage(bool compact, int layers, int messageSizeInWords, BitArray& modeMessage)
82 {
83 	modeMessage = BitArray();
84 	if (compact) {
85 		modeMessage.appendBits(layers - 1, 2);
86 		modeMessage.appendBits(messageSizeInWords - 1, 6);
87 		GenerateCheckWords(modeMessage, 28, 4, modeMessage);
88 	}
89 	else {
90 		modeMessage.appendBits(layers - 1, 5);
91 		modeMessage.appendBits(messageSizeInWords - 1, 11);
92 		GenerateCheckWords(modeMessage, 40, 4, modeMessage);
93 	}
94 }
95 
DrawModeMessage(BitMatrix & matrix,bool compact,int matrixSize,const BitArray & modeMessage)96 static void DrawModeMessage(BitMatrix& matrix, bool compact, int matrixSize, const BitArray& modeMessage)
97 {
98 	int center = matrixSize / 2;
99 	if (compact) {
100 		for (int i = 0; i < 7; i++) {
101 			int offset = center - 3 + i;
102 			if (modeMessage.get(i)) {
103 				matrix.set(offset, center - 5);
104 			}
105 			if (modeMessage.get(i + 7)) {
106 				matrix.set(center + 5, offset);
107 			}
108 			if (modeMessage.get(20 - i)) {
109 				matrix.set(offset, center + 5);
110 			}
111 			if (modeMessage.get(27 - i)) {
112 				matrix.set(center - 5, offset);
113 			}
114 		}
115 	}
116 	else {
117 		for (int i = 0; i < 10; i++) {
118 			int offset = center - 5 + i + i / 5;
119 			if (modeMessage.get(i)) {
120 				matrix.set(offset, center - 7);
121 			}
122 			if (modeMessage.get(i + 10)) {
123 				matrix.set(center + 7, offset);
124 			}
125 			if (modeMessage.get(29 - i)) {
126 				matrix.set(offset, center + 7);
127 			}
128 			if (modeMessage.get(39 - i)) {
129 				matrix.set(center - 7, offset);
130 			}
131 		}
132 	}
133 }
134 
135 ZXING_EXPORT_TEST_ONLY
StuffBits(const BitArray & bits,int wordSize,BitArray & out)136 void StuffBits(const BitArray& bits, int wordSize, BitArray& out)
137 {
138 	out = BitArray();
139 	int n = bits.size();
140 	int mask = (1 << wordSize) - 2;
141 	for (int i = 0; i < n; i += wordSize) {
142 		int word = 0;
143 		for (int j = 0; j < wordSize; j++) {
144 			if (i + j >= n || bits.get(i + j)) {
145 				word |= 1 << (wordSize - 1 - j);
146 			}
147 		}
148 		if ((word & mask) == mask) {
149 			out.appendBits(word & mask, wordSize);
150 			i--;
151 		}
152 		else if ((word & mask) == 0) {
153 			out.appendBits(word | 1, wordSize);
154 			i--;
155 		}
156 		else {
157 			out.appendBits(word, wordSize);
158 		}
159 	}
160 }
161 
TotalBitsInLayer(int layers,bool compact)162 static int TotalBitsInLayer(int layers, bool compact)
163 {
164 	return ((compact ? 88 : 112) + 16 * layers) * layers;
165 }
166 
167 /**
168 * Encodes the given binary content as an Aztec symbol
169 *
170 * @param data input data string
171 * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
172 *                      a minimum of 23% + 3 words is recommended)
173 * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
174 * @return Aztec symbol matrix with metadata
175 */
176 EncodeResult
Encode(const std::string & data,int minECCPercent,int userSpecifiedLayers)177 Encoder::Encode(const std::string& data, int minECCPercent, int userSpecifiedLayers)
178 {
179 	// High-level encode
180 	BitArray bits = HighLevelEncoder::Encode(data);
181 
182 	// stuff bits and choose symbol size
183 	int eccBits = bits.size() * minECCPercent / 100 + 11;
184 	int totalSizeBits = bits.size() + eccBits;
185 	bool compact;
186 	int layers;
187 	int totalBitsInLayer;
188 	int wordSize;
189 	BitArray stuffedBits;
190 	if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
191 		compact = userSpecifiedLayers < 0;
192 		layers = std::abs(userSpecifiedLayers);
193 		if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
194 			throw std::invalid_argument("Illegal value for layers: " + std::to_string(userSpecifiedLayers));
195 		}
196 		totalBitsInLayer = TotalBitsInLayer(layers, compact);
197 		wordSize = WORD_SIZE[layers];
198 		int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
199 		StuffBits(bits, wordSize, stuffedBits);
200 		if (stuffedBits.size() + eccBits > usableBitsInLayers) {
201 			throw std::invalid_argument("Data to large for user specified layer");
202 		}
203 		if (compact && stuffedBits.size() > wordSize * 64) {
204 			// Compact format only allows 64 data words, though C4 can hold more words than that
205 			throw std::invalid_argument("Data to large for user specified layer");
206 		}
207 	}
208 	else {
209 		wordSize = 0;
210 		// We look at the possible table sizes in the order Compact1, Compact2, Compact3,
211 		// Compact4, Normal4,...  Normal(i) for i < 4 isn't typically used since Compact(i+1)
212 		// is the same size, but has more data.
213 		for (int i = 0; ; i++) {
214 			if (i > MAX_NB_BITS) {
215 				throw std::invalid_argument("Data too large for an Aztec code");
216 			}
217 			compact = i <= 3;
218 			layers = compact ? i + 1 : i;
219 			totalBitsInLayer = TotalBitsInLayer(layers, compact);
220 			if (totalSizeBits > totalBitsInLayer) {
221 				continue;
222 			}
223 			// [Re]stuff the bits if this is the first opportunity, or if the
224 			// wordSize has changed
225 			if (wordSize != WORD_SIZE[layers]) {
226 				wordSize = WORD_SIZE[layers];
227 				StuffBits(bits, wordSize, stuffedBits);
228 			}
229 			int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
230 			if (compact && stuffedBits.size() > wordSize * 64) {
231 				// Compact format only allows 64 data words, though C4 can hold more words than that
232 				continue;
233 			}
234 			if (stuffedBits.size() + eccBits <= usableBitsInLayers) {
235 				break;
236 			}
237 		}
238 	}
239 	BitArray messageBits;
240 	GenerateCheckWords(stuffedBits, totalBitsInLayer, wordSize, messageBits);
241 
242 	// generate mode message
243 	int messageSizeInWords = stuffedBits.size() / wordSize;
244 	BitArray modeMessage;
245 	GenerateModeMessage(compact, layers, messageSizeInWords, modeMessage);
246 
247 	// allocate symbol
248 	int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
249 	std::vector<int> alignmentMap(baseMatrixSize, 0);
250 	int matrixSize;
251 	if (compact) {
252 		// no alignment marks in compact mode, alignmentMap is a no-op
253 		matrixSize = baseMatrixSize;
254 		std::iota(alignmentMap.begin(), alignmentMap.end(), 0);
255 	}
256 	else {
257 		matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
258 		int origCenter = baseMatrixSize / 2;
259 		int center = matrixSize / 2;
260 		for (int i = 0; i < origCenter; i++) {
261 			int newOffset = i + i / 15;
262 			alignmentMap[origCenter - i - 1] = center - newOffset - 1;
263 			alignmentMap[origCenter + i] = center + newOffset + 1;
264 		}
265 	}
266 
267 	EncodeResult output{compact, matrixSize, layers, messageSizeInWords, BitMatrix(matrixSize)};
268 
269 	BitMatrix& matrix = output.matrix;
270 
271 	// draw data bits
272 	for (int i = 0, rowOffset = 0; i < layers; i++) {
273 		int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
274 		for (int j = 0; j < rowSize; j++) {
275 			int columnOffset = j * 2;
276 			for (int k = 0; k < 2; k++) {
277 				if (messageBits.get(rowOffset + columnOffset + k)) {
278 					matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
279 				}
280 				if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) {
281 					matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]);
282 				}
283 				if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) {
284 					matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]);
285 				}
286 				if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) {
287 					matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]);
288 				}
289 			}
290 		}
291 		rowOffset += rowSize * 8;
292 	}
293 
294 	// draw mode message
295 	DrawModeMessage(matrix, compact, matrixSize, modeMessage);
296 
297 	// draw alignment marks
298 	if (compact) {
299 		DrawBullsEye(matrix, matrixSize / 2, 5);
300 	}
301 	else {
302 		DrawBullsEye(matrix, matrixSize / 2, 7);
303 		for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
304 			for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
305 				matrix.set(matrixSize / 2 - j, k);
306 				matrix.set(matrixSize / 2 + j, k);
307 				matrix.set(k, matrixSize / 2 - j);
308 				matrix.set(k, matrixSize / 2 + j);
309 			}
310 		}
311 	}
312 	return output;
313 }
314 
315 } // namespace ZXing::Aztec
316