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 }