1 module qrcode.encoder.encoder;
2 
3 import qrcode.common.qrcodeversion;
4 import qrcode.common.errorcorrectionlevel;
5 import qrcode.common.mode;
6 import qrcode.common.characterseteci;
7 import qrcode.common.bitarray;
8 import qrcode.common.reedsolomoncodec;
9 import qrcode.encoder.qrcode;
10 import qrcode.encoder.bytematrix;
11 import qrcode.encoder.matrixutil;
12 import qrcode.encoder.maskutil;
13 import qrcode.encoder.blockpair;
14 
15 import std.stdio;
16 import std.string;
17 import std.conv;
18 
19 import std.experimental.logger;
20 
21 /**
22 * Encoder.
23 */
24 class Encoder
25 {
26     /**
27 	* Default byte encoding.
28 	*/
29     enum DEFAULT_BYTE_MODE_ECODING = "ISO-8859-1";
30 
31     /**
32 	* The original table is defined in the table 5 of JISX0510:2004 (p.19).
33 	*
34 	* @var array
35 	*/
36     protected static int[] alphanumericTable = [
37         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
38         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
39         36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
40         0, 1,
41         2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
42         -1, 10, 11, 12,
43         13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
44         25, 26, 27, 28, 29,
45         30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
46     ];
47 
48     /**
49 	* Encodes "content" with the error correction level "ecLevel".
50 	*
51 	* @param  string               content
52 	* @param  ErrorCorrectionLevel ecLevel
53 	* @param  ?                    hints
54 	* @return QrCode
55 	*/
56     public static QrCode encode(string content, ErrorCorrectionLevel ecLevel,
57             string encoding = DEFAULT_BYTE_MODE_ECODING)
58     {
59         // Pick an encoding mode appropriate for the content. Note that this
60         // will not attempt to use multiple modes / segments even if that were
61         // more efficient. Would be nice.
62         auto mode = chooseMode(content, encoding);
63         // This will store the header information, like mode and length, as well
64         // as "header" segments like an ECI segment.
65         auto headerBits = new BitArray();
66         // Append ECI segment if applicable
67         if (mode.mode == Mode.BYTE && encoding != DEFAULT_BYTE_MODE_ECODING)
68         {
69             auto eci = CharacterSetEci.getCharacterSetEciByName(encoding);
70             //if (eci !is null) {
71             appendEci(eci, headerBits);
72             //  }
73         }
74         // (With ECI in place,) Write the mode marker
75         appendModeInfo(mode, headerBits);
76         // Collect data within the main segment, separately, to count its size
77         // if needed. Don't add it to main payload yet.
78         auto dataBits = new BitArray();
79         appendBytes(content, mode, dataBits, encoding);
80 
81         // Hard part: need to know version to know how many bits length takes.
82         // But need to know how many bits it takes to know version. First we
83         // take a guess at version by assuming version will be the minimum, 1:
84         auto provisionalBitsNeeded = headerBits.getSize() + mode.getCharacterCountBits(
85                 QrCodeVersion.getVersionForNumber(1)) + dataBits.getSize();
86 
87         auto provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel);
88 
89         // Use that guess to calculate the right version. I am still not sure
90         // this works in 100% of cases.
91         auto bitsNeeded = headerBits.getSize() + mode.getCharacterCountBits(
92                 provisionalVersion) + dataBits.getSize();
93         auto _version = chooseVersion(bitsNeeded, ecLevel);
94 
95         auto headerAndDataBits = new BitArray();
96         headerAndDataBits.appendBitArray(headerBits);
97         // Find "length" of main segment and write it.
98         auto numLetters = (mode.mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length);
99         appendLengthInfo(cast(int) numLetters, _version, mode, headerAndDataBits);
100 
101         // Put data together into the overall payload.
102         headerAndDataBits.appendBitArray(dataBits);
103 
104         auto ecBlocks = _version.getEcBlocksForLevel(ecLevel);
105         auto numDataBytes = _version.TotalCodewords() - ecBlocks.getTotalEcCodewords();
106 
107         // Terminate the bits properly.
108         terminateBits(numDataBytes, headerAndDataBits);
109 
110         // Interleave data bits with error correction code.
111         auto finalBits = interleaveWithEcBytes(headerAndDataBits,
112                 _version.TotalCodewords(), numDataBytes, ecBlocks.getNumBlocks());
113 
114         QrCode qrCode = new QrCode();
115         qrCode.errorCorrectionLevel(ecLevel);
116         qrCode.mode(mode);
117         qrCode.qrVersion(_version);
118         // Choose the mask pattern and set to "qrCode".
119         auto dimension = _version.DimensionForVersion();
120         ByteMatrix matrix = new ByteMatrix(dimension, dimension);
121         auto maskPattern = chooseMaskPattern(finalBits, ecLevel, _version, matrix);
122 
123         qrCode.maskPattern(maskPattern);
124         // Build the matrix and set it to "qrCode".
125         MatrixUtil.buildMatrix(finalBits, ecLevel, _version, maskPattern, matrix);
126 
127         qrCode.matrix(matrix);
128 
129         trace("provisionalBitsNeeded:", provisionalBitsNeeded);
130         trace("provisionalVersion:", provisionalVersion);
131         trace("QrCodeVersion.getVersionForNumber(1):", QrCodeVersion.getVersionForNumber(1));
132         trace("numLetters:", numLetters);
133         trace("content:", content);
134         trace("maskPattern:", maskPattern);
135 
136         return qrCode;
137     }
138 
139     /**
140 	* Chooses the best mode for a given content.
141 	*
142 	* @param  string content
143 	* @param  string encoding
144 	* @return Mode
145 	*/
146     protected static Mode chooseMode(string content, string encoding = string.init)
147     {
148         if (encoding.toUpper() == "SHIFT-JIS")
149         {
150             //return isOnlyDoubleByteKanji(content) ? new Mode(Mode.KANJI) : new Mode(Mode.BYTE);
151 
152             return new Mode(Mode.KANJI);
153         }
154 
155         auto hasNumeric = false;
156         auto hasAlphanumeric = false;
157 
158         import std.ascii;
159 
160         for (auto i = 0; i < content.length; i++)
161         {
162             auto c = content[i];
163             if (isDigit(c))
164             {
165                 hasNumeric = true;
166             }
167             else if (getAlphanumericCode(c) != -1)
168             {
169                 hasAlphanumeric = true;
170             }
171             else
172             {
173                 return new Mode(Mode.BYTE);
174             }
175         }
176         if (hasAlphanumeric)
177         {
178             return new Mode(Mode.ALPHANUMERIC);
179         }
180         else if (hasNumeric)
181         {
182             return new Mode(Mode.NUMERIC);
183         }
184         return new Mode(Mode.BYTE);
185     }
186 
187     /**
188 	* Gets the alphanumeric code for a byte.
189 	*
190 	* @param  string|integer $code
191 	* @return integer
192 	*/
193     protected static int getAlphanumericCode(int code)
194     {
195         if (alphanumericTable.length > code)
196         {
197             return alphanumericTable[code];
198         }
199         return -1;
200     }
201 
202     /**
203 	* Appends ECI information to a bit array.
204 	*
205 	* @param  CharacterSetEci $eci
206 	* @param  BitArray        $bits
207 	* @return void
208 	*/
209     protected static void appendEci(CharacterSetEci eci, ref BitArray bits)
210     {
211         auto mode = new Mode(Mode.ECI);
212         bits.appendBits(mode.mode, 4);
213         bits.appendBits(eci.eci, 8);
214     }
215 
216     /**
217 	* Appends mode information to a bit array.
218 	*
219 	* @param  Mode     $mode
220 	* @param  BitArray $bits
221 	* @return void
222 	*/
223     protected static void appendModeInfo(Mode mode, ref BitArray bits)
224     {
225         bits.appendBits(mode.mode, 4);
226     }
227 
228     /**
229 	* Appends bytes to a bit array in a specific mode.
230 	*
231 	* @param  stirng   $content
232 	* @param  Mode     $mode
233 	* @param  BitArray $bits
234 	* @param  string   $encoding
235 	* @return void
236 	* @throws Exception\WriterException
237 	*/
238     protected static void appendBytes(string content, Mode mode, ref BitArray bits, string encoding)
239     {
240         switch (mode.mode)
241         {
242         case Mode.NUMERIC:
243             appendNumericBytes(content, bits);
244             break;
245         case Mode.ALPHANUMERIC:
246             appendAlphanumericBytes(content, bits);
247             break;
248         case Mode.BYTE:
249             append8BitBytes(content, bits, encoding);
250             break;
251         case Mode.KANJI:
252             appendKanjiBytes(content, bits);
253             break;
254         default:
255             throw new Exception("Invalid mode: " ~ to!string(mode.mode));
256         }
257     }
258 
259     /**
260 	* Appends numeric bytes to a bit array.
261 	*
262 	* @param  string   content
263 	* @param  BitArray bits
264 	* @return void
265 	*/
266     protected static void appendNumericBytes(string content, BitArray bits)
267     {
268         import std.conv, qrcode.utils;
269 
270         auto length = content.length;
271         int i = 0;
272         while (i < length)
273         {
274             auto num1 = charToInt(content[i]);
275             if (i + 2 < length)
276             {
277                 // Encode three numeric letters in ten bits.
278                 auto num2 = charToInt(content[i + 1]);
279                 auto num3 = charToInt(content[i + 2]);
280                 bits.appendBits(num1 * 100 + num2 * 10 + num3, 10);
281                 i += 3;
282             }
283             else if (i + 1 < length)
284             {
285                 // Encode two numeric letters in seven bits.
286                 auto num2 = charToInt(content[i + 1]);
287                 bits.appendBits(num1 * 10 + num2, 7);
288                 i += 2;
289             }
290             else
291             {
292                 // Encode one numeric letter in four bits.
293                 bits.appendBits(num1, 4);
294                 i++;
295             }
296         }
297     }
298 
299     /**
300 	* Appends alpha-numeric bytes to a bit array.
301 	*
302 	* @param  string   content
303 	* @param  BitArray bits
304 	* @return void
305 	*/
306     protected static void appendAlphanumericBytes(string content, BitArray bits)
307     {
308         auto i = 0;
309         auto code1 = 0, code2 = 0, length = content.length;
310         while (i < length)
311         {
312             if (-1 == (code1 = getAlphanumericCode(content[i])))
313             {
314                 throw new Exception("Invalid alphanumeric code");
315             }
316             if (i + 1 < length)
317             {
318                 if (-1 == (code2 = getAlphanumericCode(content[i + 1])))
319                 {
320                     throw new Exception("Invalid alphanumeric code");
321                 }
322                 // Encode two alphanumeric letters in 11 bits.
323                 bits.appendBits(code1 * 45 + code2, 11);
324                 i += 2;
325             }
326             else
327             {
328                 // Encode one alphanumeric letter in six bits.
329                 bits.appendBits(code1, 6);
330                 i++;
331             }
332         }
333     }
334 
335     /**
336 	* Appends regular 8-bit bytes to a bit array.
337 	*
338 	* @param  string   content
339 	* @param  BitArray bits
340 	* @return void
341 	*/
342     protected static void append8BitBytes(string content, ref BitArray bits, string encoding)
343     {
344         /*   if (false == (bytes = @iconv('utf-8', encoding, content))) {
345             throw new Exception("Could not encode content to " ~ encoding);
346         }
347 		*/
348         trace("content:----", content);
349 	    
350         auto length = content.length;
351         for (auto i = 0; i < length; i++)
352         {
353             bits.appendBits(cast(int)(content[i]), 8, true);
354         }
355 
356     }
357 
358     /**
359 	* Appends KANJI bytes to a bit array.
360 	*
361 	* @param  string   content
362 	* @param  BitArray bits
363 	* @return void
364 	*/
365     protected static void appendKanjiBytes(string content, BitArray bits)
366     {
367         if (content.length % 2 > 0)
368         {
369             // We just do a simple length check here. The for loop will check
370             // individual characters.
371             throw new Exception("Content does not seem to be encoded in SHIFT-JIS");
372         }
373 
374         int subtracted = 0;
375 
376         for (auto i = 0; i < content.length; i += 2)
377         {
378             auto byte1 = (content[i]) & 0xff;
379             auto byte2 = (content[i + 1]) & 0xff;
380             auto code = (byte1 << 8) | byte2;
381             if (code >= 0x8140 && code <= 0x9ffc)
382             {
383                 subtracted = code - 0x8140;
384             }
385             else if (code >= 0xe040 && code <= 0xebbf)
386             {
387                 subtracted = code - 0xc140;
388             }
389             else
390             {
391                 throw new Exception("Invalid byte sequence");
392             }
393             auto encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
394             bits.appendBits(encoded, 13);
395         }
396     }
397 
398     /**
399 	* Chooses the best version for the input.
400 	*
401 	* @param  integer              numInputBits
402 	* @param  ErrorCorrectionLevel ecLevel
403 	* @return Version
404 	* @throws Exception\WriterException
405 	*/
406     protected static QrCodeVersion chooseVersion(BitArrayBitType numInputBits,
407             ErrorCorrectionLevel ecLevel)
408     {
409         for (auto versionNum = 1; versionNum <= 40; versionNum++)
410         {
411             QrCodeVersion _version = QrCodeVersion.getVersionForNumber(versionNum);
412             auto numBytes = _version.TotalCodewords();
413             auto ecBlocks = _version.getEcBlocksForLevel(ecLevel);
414             auto numEcBytes = ecBlocks.getTotalEcCodewords();
415             auto numDataBytes = numBytes - numEcBytes;
416             auto totalInputBytes = to!int((numInputBits + 8) / 8);
417             if (numDataBytes >= totalInputBytes)
418             {
419                 return _version;
420             }
421         }
422         throw new Exception("Data too big");
423     }
424 
425     /**
426 	* Appends length information to a bit array.
427 	*
428 	* @param  integer  numLetters
429 	* @param  Version  version
430 	* @param  Mode     mode
431 	* @param  BitArray bits
432 	* @return void
433 	* @throws Exception\WriterException
434 	*/
435     protected static void appendLengthInfo(int numLetters,
436             QrCodeVersion _version, Mode mode, ref BitArray bits)
437     {
438         auto numBits = mode.getCharacterCountBits(_version);
439         if (numLetters >= (1 << numBits))
440         {
441             throw new Exception(to!string(
442                     numLetters) ~ " is bigger than " ~ to!string(((1 << numBits) - 1)));
443         }
444         bits.appendBits(numLetters, numBits);
445     }
446 
447     /**
448 	* Terminates the bits in a bit array.
449 	*
450 	* @param  integer  numDataBytes
451 	* @param  BitArray bits
452 	* @throws Exception\WriterException
453 	*/
454     protected static void terminateBits(int numDataBytes, ref BitArray bits)
455     {
456         auto capacity = numDataBytes << 3;
457         if (bits.getSize() > capacity)
458         {
459             throw new Exception("Data bits cannot fit in the QR code");
460         }
461         for (auto i = 0; i < 4 && bits.getSize() < capacity; i++)
462         {
463             bits.appendBit(false);
464         }
465         auto numBitsInLastByte = bits.getSize() & 0x7;
466         if (numBitsInLastByte > 0)
467         {
468             for (auto i = numBitsInLastByte; i < 8; i++)
469             {
470                 bits.appendBit(false);
471             }
472         }
473         auto numPaddingBytes = numDataBytes - bits.getSizeInBytes();
474         for (auto i = 0; i < numPaddingBytes; i++)
475         {
476             bits.appendBits((i & 0x1) == 0 ? 0xec : 0x11, 8);
477         }
478         if (bits.getSize() != capacity)
479         {
480             throw new Exception("Bits size does not equal capacity");
481         }
482     }
483 
484     /**
485 	* Interleaves data with EC bytes.
486 	*
487 	* @param  BitArray bits
488 	* @param  integer  numTotalBytes
489 	* @param  integer  numDataBytes
490 	* @param  integer  numRsBlocks
491 	* @return BitArray
492 	* @throws Exception\WriterException
493 	*/
494     protected static BitArray interleaveWithEcBytes(ref BitArray bits,
495             int numTotalBytes, int numDataBytes, int numRsBlocks)
496     {
497         if (bits.getSizeInBytes() != numDataBytes)
498         {
499             throw new Exception("Number of bits and data bytes does not match");
500         }
501         auto dataBytesOffset = 0;
502         auto maxNumDataBytes = 0;
503         auto maxNumEcBytes = 0;
504         BlockPair[] blocks = new BlockPair[numRsBlocks];
505         for (auto i = 0; i < numRsBlocks; i++)
506         {
507             auto tmp = getNumDataBytesAndNumEcBytesForBlockId(numTotalBytes,
508                     numDataBytes, numRsBlocks, i);
509             auto numDataBytesInBlock = tmp[0];
510             auto numEcBytesInBlock = tmp[1];
511             auto size = numDataBytesInBlock;
512             auto dataBytes = bits.toBytes(8 * dataBytesOffset, size);
513             auto ecBytes = generateEcBytes(dataBytes, numEcBytesInBlock);
514             blocks[i] = new BlockPair(dataBytes, ecBytes);
515             import std.algorithm;
516 
517             maxNumDataBytes = max(maxNumDataBytes, size);
518             maxNumEcBytes = max(maxNumEcBytes, cast(int) ecBytes.length);
519             dataBytesOffset += numDataBytesInBlock;
520         }
521         if (numDataBytes != dataBytesOffset)
522         {
523             throw new Exception("Data bytes does not match offset");
524         }
525         auto result = new BitArray();
526         for (auto i = 0; i < maxNumDataBytes; i++)
527         {
528             foreach (block; blocks)
529             {
530                 auto dataBytes = block.getDataBytes();
531                 if (i < dataBytes.length)
532                 {
533                     result.appendBits(dataBytes[i], 8);
534                 }
535             }
536         }
537         for (auto i = 0; i < maxNumEcBytes; i++)
538         {
539             foreach (block; blocks)
540             {
541                 auto ecBytes = block.getErrorCorrectionBytes();
542                 if (i < ecBytes.length)
543                 {
544                     result.appendBits(ecBytes[i], 8);
545                 }
546             }
547         }
548         if (numTotalBytes != result.getSizeInBytes())
549         {
550             throw new Exception("Interleaving error: " ~ to!string(
551                     numTotalBytes) ~ " and " ~ to!string(result.getSizeInBytes()) ~ " differ");
552         }
553         return result;
554     }
555     /**
556 	* Gets number of data- and EC bytes for a block ID.
557 	*
558 	* @param  integer numTotalBytes
559 	* @param  integer numDataBytes
560 	* @param  integer numRsBlocks
561 	* @param  integer blockId
562 	* @return array
563 	* @throws Exception\WriterException
564 	*/
565     protected static int[] getNumDataBytesAndNumEcBytesForBlockId(int numTotalBytes,
566             int numDataBytes, int numRsBlocks, int blockId)
567     {
568         if (blockId >= numRsBlocks)
569         {
570             throw new Exception("Block ID too large");
571         }
572         auto numRsBlocksInGroup2 = numTotalBytes % numRsBlocks;
573         auto numRsBlocksInGroup1 = numRsBlocks - numRsBlocksInGroup2;
574         auto numTotalBytesInGroup1 = to!int(numTotalBytes / numRsBlocks);
575         auto numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;
576         auto numDataBytesInGroup1 = to!int(numDataBytes / numRsBlocks);
577         auto numDataBytesInGroup2 = numDataBytesInGroup1 + 1;
578         auto numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1;
579         auto numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2;
580         if (numEcBytesInGroup1 != numEcBytesInGroup2)
581         {
582             throw new Exception("EC bytes mismatch");
583         }
584         if (numRsBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2)
585         {
586             throw new Exception("RS blocks mismatch");
587         }
588         if (numTotalBytes != ((numDataBytesInGroup1 + numEcBytesInGroup1) * numRsBlocksInGroup1) + (
589                 (numDataBytesInGroup2 + numEcBytesInGroup2) * numRsBlocksInGroup2))
590         {
591             throw new Exception("Total bytes mismatch");
592         }
593         if (blockId < numRsBlocksInGroup1)
594         {
595             return [numDataBytesInGroup1, numEcBytesInGroup1];
596         }
597         else
598         {
599             return [numDataBytesInGroup2, numEcBytesInGroup2];
600         }
601     }
602 
603     /**
604 	* Generates EC bytes for given data.
605 	*
606 	* @param  SplFixedArray dataBytes
607 	* @param  integer       numEcBytesInBlock
608 	* @return SplFixedArray
609 	*/
610     protected static int[] generateEcBytes(int[] dataBytes, int numEcBytesInBlock)
611     {
612         auto numDataBytes = cast(int) dataBytes.length;
613         auto toEncode = new int[numDataBytes + numEcBytesInBlock];
614         for (auto i = 0; i < numDataBytes; i++)
615         {
616             toEncode[i] = dataBytes[i] & 0xff;
617         }
618         auto ecBytes = new int[numEcBytesInBlock];
619         ReedSolomonCodec codec = getCodec(numDataBytes, numEcBytesInBlock);
620         codec.encode(toEncode, ecBytes);
621         return ecBytes;
622     }
623 
624     /**
625 	* Gets an RS codec and caches it.
626 	*
627 	* @param  integer numDataBytes
628 	* @param  integer numEcBytesInBlock
629 	* @return ReedSolomonCodec
630 	*/
631     protected static ReedSolomonCodec getCodec(int numDataBytes, int numEcBytesInBlock)
632     {
633         return new ReedSolomonCodec(8, 0x11d, 0, 1, numEcBytesInBlock,
634                 255 - numDataBytes - numEcBytesInBlock);
635 
636     }
637     /**
638 	* Chooses the best mask pattern for a matrix.
639 	*
640 	* @param  BitArray             bits
641 	* @param  ErrorCorrectionLevel ecLevel
642 	* @param  Version              version
643 	* @param  ByteMatrix           matrix
644 	* @return integer
645 	*/
646     protected static int chooseMaskPattern(BitArray bits,
647             ErrorCorrectionLevel ecLevel, QrCodeVersion _version, ref ByteMatrix matrix)
648     {
649         auto minPenality = int.max;
650         auto bestMaskPattern = -1;
651         for (auto maskPattern = 0; maskPattern < QrCode.NUM_MASK_PATTERNS; maskPattern++)
652         {
653             MatrixUtil.buildMatrix(bits, ecLevel, _version, maskPattern, matrix);
654             auto penalty = calculateMaskPenalty(matrix);
655             if (penalty < minPenality)
656             {
657                 minPenality = penalty;
658                 bestMaskPattern = maskPattern;
659             }
660             import std.stdio;
661 
662             //writeln(matrix);
663         }
664         return bestMaskPattern;
665     }
666 
667     /**
668 	* Calculates the mask penalty for a matrix.
669 	*
670 	* @param  ByteMatrix $matrix
671 	* @return integer
672 	*/
673     protected static int calculateMaskPenalty(ByteMatrix matrix)
674     {
675         return (MaskUtil.applyMaskPenaltyRule1(matrix) + MaskUtil.applyMaskPenaltyRule2(
676                 matrix) + MaskUtil.applyMaskPenaltyRule3(
677                 matrix) + MaskUtil.applyMaskPenaltyRule4(matrix));
678     }
679 }