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 }