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