diff --git a/plugins/two-factor-auth/index.php b/plugins/two-factor-auth/index.php
index 5c0dcc22a..d212f6c68 100644
--- a/plugins/two-factor-auth/index.php
+++ b/plugins/two-factor-auth/index.php
@@ -7,9 +7,9 @@ class TwoFactorAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Two Factor Authentication',
- VERSION = '2.12',
+ VERSION = '2.12.3',
RELEASE = '2022-02-16',
- REQUIRED = '2.12.0',
+ REQUIRED = '2.12.3',
CATEGORY = 'Login',
DESCRIPTION = 'Provides support for TOTP 2FA';
@@ -117,6 +117,15 @@ class TwoFactorAuthPlugin extends \RainLoop\Plugins\AbstractPlugin
$aResult = $this->getTwoFactorInfo($oAccount);
unset($aResult['BackupCodes']);
+ $name = \rawurlencode($oAccount->Email());
+// $issuer = \rawurlencode(\RainLoop\API::Config()->Get('webmail', 'title', 'SnappyMail'));
+ $QR = \SnappyMail\QRCode::getMinimumQRCode(
+// "otpauth://totp/{$issuer}:{$name}?secret={$aResult['Secret']}&issuer={$issuer}",
+ "otpauth://totp/{$name}?secret={$aResult['Secret']}",
+ \SnappyMail\QRCode::ERROR_CORRECT_LEVEL_M
+ );
+ $aResult['QRCode'] = $QR->__toString();
+
return $this->jsonResponse(__FUNCTION__, $aResult);
}
diff --git a/plugins/two-factor-auth/js/TwoFactorAuthSettings.js b/plugins/two-factor-auth/js/TwoFactorAuthSettings.js
index 4c3b8e9a4..2af8d65f4 100644
--- a/plugins/two-factor-auth/js/TwoFactorAuthSettings.js
+++ b/plugins/two-factor-auth/js/TwoFactorAuthSettings.js
@@ -45,9 +45,8 @@ class TwoFactorAuthSettings
this.twoFactorTested = ko.observable(false);
this.viewSecret = ko.observable('');
+ this.viewQRCode = ko.observable('');
this.viewBackupCodes = ko.observable('');
- this.viewUrlTitle = ko.observable('');
- this.viewUrl = ko.observable('');
this.viewEnable_ = ko.observable(false);
@@ -102,9 +101,8 @@ class TwoFactorAuthSettings
hideSecret() {
this.viewSecret('');
+ this.viewQRCode('');
this.viewBackupCodes('');
- this.viewUrlTitle('');
- this.viewUrl('');
}
createTwoFactor() {
@@ -121,10 +119,7 @@ class TwoFactorAuthSettings
}
clearTwoFactor() {
- this.viewSecret('');
- this.viewBackupCodes('');
- this.viewUrlTitle('');
- this.viewUrl('');
+ this.hideSecret();
this.twoFactorTested(false);
@@ -134,11 +129,7 @@ class TwoFactorAuthSettings
onShow(bLock) {
this.lock(!!bLock);
-
- this.viewSecret('');
- this.viewBackupCodes('');
- this.viewUrlTitle('');
- this.viewUrl('');
+ this.hideSecret('');
}
onHide() {
@@ -162,11 +153,7 @@ class TwoFactorAuthSettings
this.viewEnable_(false);
this.twoFactorStatus(false);
this.twoFactorTested(false);
-
- this.viewSecret('');
- this.viewBackupCodes('');
- this.viewUrlTitle('');
- this.viewUrl('');
+ this.hideSecret('');
} else {
this.viewUser(pString(oData.Result.User));
this.viewEnable_(!!oData.Result.Enable);
@@ -174,10 +161,8 @@ class TwoFactorAuthSettings
this.twoFactorTested(!!oData.Result.Tested);
this.viewSecret(pString(oData.Result.Secret));
+ this.viewQRCode(oData.Result.QRCode);
this.viewBackupCodes(pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' '));
-
- this.viewUrlTitle(pString(oData.Result.UrlTitle));
- this.viewUrl(null/*qr.toDataURL({ level: 'M', size: 8, value: this.getQr() })*/);
}
}
@@ -186,12 +171,10 @@ class TwoFactorAuthSettings
if (iError) {
this.viewSecret('');
- this.viewUrlTitle('');
- this.viewUrl('');
+ this.viewQRCode('');
} else {
this.viewSecret(pString(data.Result.Secret));
- this.viewUrlTitle(pString(data.Result.UrlTitle));
- this.viewUrl(null/*qr.toDataURL({ level: 'M', size: 6, value: this.getQr() })*/);
+ this.viewQRCode(pString(data.Result.QRCode));
}
}
diff --git a/plugins/two-factor-auth/templates/TwoFactorAuthSettings.html b/plugins/two-factor-auth/templates/TwoFactorAuthSettings.html
index 49ff5b42c..096f613ac 100644
--- a/plugins/two-factor-auth/templates/TwoFactorAuthSettings.html
+++ b/plugins/two-factor-auth/templates/TwoFactorAuthSettings.html
@@ -52,9 +52,7 @@
-
-
-
+
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/qrcode.php b/snappymail/v/0.0.0/app/libraries/snappymail/qrcode.php
new file mode 100644
index 000000000..338ae3d3d
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/qrcode.php
@@ -0,0 +1,1354 @@
+__toString()
+//---------------------------------------------------------------------
+
+namespace SnappyMail;
+
+class QRCode
+{
+
+ const
+ ERROR_CORRECT_LEVEL_L = 1, // 7%
+ ERROR_CORRECT_LEVEL_M = 0, // 15%
+ ERROR_CORRECT_LEVEL_Q = 3, // 25%
+ ERROR_CORRECT_LEVEL_H = 2; // 30%
+
+ protected
+ $typeNumber = 1,
+ $modules,
+ $moduleCount,
+ $errorCorrectLevel = self::ERROR_CORRECT_LEVEL_H,
+ $qrDataList = [];
+
+ public function __toString() : string
+ {
+ $qrUnicode = '';
+ $moduleCount = $this->getModuleCount();
+ for ($r = 0; $r < $moduleCount; $r += 2) {
+ for ($c = 0; $c < $moduleCount; ++$c) {
+ $w2 = ($r+1 >= $moduleCount || !$this->isDark($r+1, $c));
+ if ($this->isDark($r, $c)) {
+ $qrUnicode .= ($w2 ? '▀' : '█'); // upper half block : full block
+ } else {
+ $qrUnicode .= ($w2 ? ' ' : '▄'); // nobreak space : lower half block
+ }
+ }
+ $qrUnicode .= "\n";
+ }
+ return $qrUnicode;
+ }
+
+ public function __get($k)
+ {
+ if (\property_exists($this, $k)) {
+ return $this->$k;
+ }
+ }
+
+ public function __set($k, $v)
+ {
+ switch ($k)
+ {
+ case 'typeNumber':
+ $this->setTypeNumber($v);
+ break;
+ case 'errorCorrectLevel':
+ $this->setErrorCorrectLevel($v);
+ break;
+ }
+ }
+
+ public function setTypeNumber(int $typeNumber) : self
+ {
+ if (1 > $typeNumber || 40 < $typeNumber) {
+ throw new \OutOfBoundsException("Invalid typeNumber: {$typeNumber}");
+ }
+ $this->typeNumber = $typeNumber;
+ return $this;
+ }
+
+ public function setErrorCorrectLevel(int $errorCorrectLevel) : self
+ {
+ if (0 > $errorCorrectLevel || 3 < $errorCorrectLevel) {
+ throw new \OutOfBoundsException("Invalid errorCorrectLevel: {$errorCorrectLevel}");
+ }
+ $this->errorCorrectLevel = $errorCorrectLevel;
+ return $this;
+ }
+
+ public function addData($data, int $mode = 0) : void
+ {
+ $this->qrDataList[] = new QRData($data, $mode);
+ }
+
+ public function clearData() : void
+ {
+ $this->qrDataList = [];
+ }
+
+ public function getDataCount() : int
+ {
+ return \count($this->qrDataList);
+ }
+
+ public function getData(int $index) : QRData
+ {
+ return $this->qrDataList[$index];
+ }
+
+ protected function makeImpl(bool $test, int $maskPattern) : void
+ {
+ $this->moduleCount = $this->typeNumber * 4 + 17;
+
+ $this->modules = \array_fill(0, $this->moduleCount, \array_fill(0, $this->moduleCount, null));
+
+ $this->setupPositionProbePattern(0, 0);
+ $this->setupPositionProbePattern($this->moduleCount - 7, 0);
+ $this->setupPositionProbePattern(0, $this->moduleCount - 7);
+
+ $this->setupPositionAdjustPattern();
+ $this->setupTimingPattern();
+
+ $this->setupTypeInfo($test, $maskPattern);
+
+ if ($this->typeNumber >= 7) {
+ $bits = QRUtil::getBCHTypeNumber($this->typeNumber);
+ for ($i = 0; $i < 18; ++$i) {
+ $mod = (!$test && (($bits >> $i) & 1) == 1);
+ $this->modules[(int)\floor($i / 3)][$i % 3 + $this->moduleCount - 8 - 3] = $mod;
+ }
+ for ($i = 0; $i < 18; ++$i) {
+ $mod = (!$test && (($bits >> $i) & 1) == 1);
+ $this->modules[$i % 3 + $this->moduleCount - 8 - 3][\floor($i / 3)] = $mod;
+ }
+ }
+
+ $dataArray = $this->qrDataList;
+
+ $data = self::createData($this->typeNumber, $this->errorCorrectLevel, $dataArray);
+
+ $this->mapData($data, $maskPattern);
+ }
+
+ protected function setupPositionProbePattern(int $row, int $col)
+ {
+ for ($r = -1; $r <= 7; ++$r) {
+ for ($c = -1; $c <= 7; ++$c) {
+ if ($row + $r <= -1 || $this->moduleCount <= $row + $r
+ || $col + $c <= -1 || $this->moduleCount <= $col + $c) {
+ continue;
+ }
+ $this->modules[$row + $r][$col + $c] = (
+ (0 <= $r && $r <= 6 && ($c == 0 || $c == 6))
+ || (0 <= $c && $c <= 6 && ($r == 0 || $r == 6))
+ || (2 <= $r && $r <= 4 && 2 <= $c && $c <= 4)
+ );
+ }
+ }
+ }
+
+ protected function getBestMaskPattern() : int
+ {
+ $minLostPoint = 0;
+ $pattern = 0;
+ for ($i = 0; $i < 8; ++$i) {
+ $this->makeImpl(true, $i);
+ $lostPoint = QRUtil::getLostPoint($this);
+ if ($i == 0 || $minLostPoint > $lostPoint) {
+ $minLostPoint = $lostPoint;
+ $pattern = $i;
+ }
+ }
+ return $pattern;
+ }
+
+ protected function setupTimingPattern() : void
+ {
+ for ($r = 8; $r < $this->moduleCount - 8; ++$r) {
+ if ($this->modules[$r][6] !== null) {
+ continue;
+ }
+ $this->modules[$r][6] = ($r % 2 == 0);
+ }
+
+ for ($c = 8; $c < $this->moduleCount - 8; ++$c) {
+ if ($this->modules[6][$c] !== null) {
+ continue;
+ }
+ $this->modules[6][$c] = ($c % 2 == 0);
+ }
+ }
+
+ protected function setupPositionAdjustPattern() : void
+ {
+ $pos = QRUtil::getPatternPosition($this->typeNumber);
+ $cnt = \count($pos);
+ for ($i = 0; $i < $cnt; ++$i) {
+ for ($j = 0; $j < $cnt; ++$j) {
+ $row = $pos[$i];
+ $col = $pos[$j];
+ if ($this->modules[$row][$col] !== null) {
+ continue;
+ }
+ for ($r = -2; $r <= 2; ++$r) {
+ for ($c = -2; $c <= 2; ++$c) {
+ $this->modules[$row + $r][$col + $c] = ($r == -2 || $r == 2 || $c == -2 || $c == 2 || ($r == 0 && $c == 0));
+ }
+ }
+ }
+ }
+ }
+
+ protected function setupTypeInfo(bool $test, int $maskPattern) : void
+ {
+ $bits = QRUtil::getBCHTypeInfo(($this->errorCorrectLevel << 3) | $maskPattern);
+
+ // vertical
+ for ($i = 0; $i < 15; ++$i) {
+ $mod = (!$test && (($bits >> $i) & 1) == 1);
+ if ($i < 6) {
+ $this->modules[$i][8] = $mod;
+ } else if ($i < 8) {
+ $this->modules[$i + 1][8] = $mod;
+ } else {
+ $this->modules[$this->moduleCount - 15 + $i][8] = $mod;
+ }
+ }
+
+ // horizontal
+ for ($i = 0; $i < 15; ++$i) {
+ $mod = (!$test && (($bits >> $i) & 1) == 1);
+ if ($i < 8) {
+ $this->modules[8][$this->moduleCount - $i - 1] = $mod;
+ } else if ($i < 9) {
+ $this->modules[8][15 - $i - 1 + 1] = $mod;
+ } else {
+ $this->modules[8][15 - $i - 1] = $mod;
+ }
+ }
+
+ // fixed module
+ $this->modules[$this->moduleCount - 8][8] = !$test;
+ }
+
+ protected function mapData(&$data, int $maskPattern) : void
+ {
+ $inc = -1;
+ $row = $this->moduleCount - 1;
+ $bitIndex = 7;
+ $byteIndex = 0;
+ $length = \count($data);
+ for ($col = $this->moduleCount - 1; $col > 0; $col -= 2) {
+ if ($col == 6) --$col;
+ while (true) {
+ for ($c = 0; $c < 2; ++$c) {
+ if ($this->modules[$row][$col - $c] === null) {
+ $dark = false;
+ if ($byteIndex < $length) {
+ $dark = ((($data[$byteIndex] >> $bitIndex) & 1) == 1);
+ }
+ if (QRUtil::isMask($maskPattern, $row, $col - $c)) {
+ $dark = !$dark;
+ }
+ $this->modules[$row][$col - $c] = $dark;
+ --$bitIndex;
+ if ($bitIndex == -1) {
+ ++$byteIndex;
+ $bitIndex = 7;
+ }
+ }
+ }
+ $row += $inc;
+ if ($row < 0 || $this->moduleCount <= $row) {
+ $row -= $inc;
+ $inc = -$inc;
+ break;
+ }
+ }
+ }
+ }
+
+ protected static function createBytes(&$buffer, &$rsBlocks) : array
+ {
+ $offset = 0;
+ $maxDcCount = 0;
+ $maxEcCount = 0;
+ $dcdata = [];
+ $ecdata = [];
+ $cntBlocks = \count($rsBlocks);
+
+ for ($r = 0; $r < $cntBlocks; ++$r) {
+
+ $dcCount = $rsBlocks[$r]->getDataCount();
+ $ecCount = $rsBlocks[$r]->getTotalCount() - $dcCount;
+
+ $maxDcCount = \max($maxDcCount, $dcCount);
+ $maxEcCount = \max($maxEcCount, $ecCount);
+
+ $dcdata[$r] = [];
+ for ($i = 0; $i < $dcCount; ++$i) {
+ $bdata = $buffer->getBuffer();
+ $dcdata[$r][$i] = 0xff & $bdata[$i + $offset];
+ }
+ $offset += $dcCount;
+
+ $rsPoly = QRUtil::getErrorCorrectPolynomial($ecCount);
+ $rawPoly = new QRPolynomial($dcdata[$r], $rsPoly->getLength() - 1);
+
+ $modPoly = $rawPoly->mod($rsPoly);
+ $rsCount = $rsPoly->getLength() - 1;
+ $ecdata[$r] = [];
+ for ($i = 0; $i < $rsCount; ++$i) {
+ $modIndex = $i + $modPoly->getLength() - $rsCount;
+ $ecdata[$r][$i] = ($modIndex >= 0)? $modPoly->get($modIndex) : 0;
+ }
+ }
+
+ $totalCodeCount = 0;
+ for ($i = 0; $i < $cntBlocks; ++$i) {
+ $totalCodeCount += $rsBlocks[$i]->getTotalCount();
+ }
+
+ $data = \array_fill(0, $totalCodeCount, null);
+
+ $index = 0;
+
+ for ($i = 0; $i < $maxDcCount; ++$i) {
+ for ($r = 0; $r < $cntBlocks; ++$r) {
+ if ($i < \count($dcdata[$r])) {
+ $data[$index++] = $dcdata[$r][$i];
+ }
+ }
+ }
+
+ for ($i = 0; $i < $maxEcCount; ++$i) {
+ for ($r = 0; $r < $cntBlocks; ++$r) {
+ if ($i < \count($ecdata[$r])) {
+ $data[$index++] = $ecdata[$r][$i];
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ protected static function createData(int $typeNumber, int $errorCorrectLevel, $dataArray) : array
+ {
+ $rsBlocks = QRRSBlock::getRSBlocks($typeNumber, $errorCorrectLevel);
+ $buffer = new QRBitBuffer();
+ $cnt = \count($dataArray);
+ for ($i = 0; $i < $cnt; ++$i) {
+ $data = $dataArray[$i];
+ $buffer->put($data->getMode(), 4);
+ $buffer->put($data->getLength(), $data->getLengthInBits($typeNumber));
+ $data->write($buffer);
+ }
+
+ $totalDataCount = 0;
+ $cnt = \count($rsBlocks);
+ for ($i = 0; $i < $cnt; ++$i) {
+ $totalDataCount += $rsBlocks[$i]->getDataCount();
+ }
+
+ if ($buffer->getLengthInBits() > $totalDataCount * 8) {
+ throw new \OutOfBoundsException("code length overflow. ("
+ . $buffer->getLengthInBits()
+ . ">"
+ . $totalDataCount * 8
+ . ")");
+ }
+
+ // end code.
+ if ($buffer->getLengthInBits() + 4 <= $totalDataCount * 8) {
+ $buffer->put(0, 4);
+ }
+
+ // padding
+ while ($buffer->getLengthInBits() % 8 != 0) {
+ $buffer->putBit(false);
+ }
+
+ // padding
+ while (true) {
+
+ if ($buffer->getLengthInBits() >= $totalDataCount * 8) {
+ break;
+ }
+ $buffer->put(0xEC, 8); // QR_PAD0
+
+ if ($buffer->getLengthInBits() >= $totalDataCount * 8) {
+ break;
+ }
+ $buffer->put(0x11, 8); // QR_PAD1
+ }
+
+ return self::createBytes($buffer, $rsBlocks);
+ }
+
+ public function isDark(int $row, int $col) : bool
+ {
+ return ($this->modules[$row][$col] !== null) && $this->modules[$row][$col];
+ }
+
+ public function getModuleCount() : int
+ {
+ return $this->moduleCount;
+ }
+
+ public function make() : void
+ {
+ $this->makeImpl(false, $this->getBestMaskPattern());
+ }
+
+ public static function getMinimumQRCode($data, int $errorCorrectLevel) : self
+ {
+ $qr = new QRCode();
+ $qr->setErrorCorrectLevel($errorCorrectLevel);
+ $qr->addData($data);
+
+ $qrData = $qr->getData(0);
+ $length = $qrData->getLength();
+ $mode = $qrData->getMode();
+
+ for ($typeNumber = 1; $typeNumber <= 10; ++$typeNumber) {
+ if ($length <= QRUtil::getMaxLength($typeNumber, $mode, $errorCorrectLevel)) {
+ $qr->setTypeNumber($typeNumber);
+ break;
+ }
+ }
+
+ $qr->make();
+
+ return $qr;
+ }
+}
+
+//---------------------------------------------------------------
+// QRUtil
+//---------------------------------------------------------------
+
+abstract class QRUtil {
+
+ const
+ MASK_PATTERN000 = 0,
+ MASK_PATTERN001 = 1,
+ MASK_PATTERN010 = 2,
+ MASK_PATTERN011 = 3,
+ MASK_PATTERN100 = 4,
+ MASK_PATTERN101 = 5,
+ MASK_PATTERN110 = 6,
+ MASK_PATTERN111 = 7,
+
+ G15 = 1335,
+ G18 = 7973,
+ G15_MASK = 21522;
+
+ static $QR_MAX_LENGTH = [
+ [[41, 25, 17, 10], [34, 20, 14, 8], [27, 16, 11, 7], [17, 10, 7, 4]],
+ [[77, 47, 32, 20], [63, 38, 26, 16], [48, 29, 20, 12], [34, 20, 14, 8]],
+ [[127, 77, 53, 32], [101, 61, 42, 26], [77, 47, 32, 20], [58, 35, 24, 15]],
+ [[187, 114, 78, 48], [149, 90, 62, 38], [111, 67, 46, 28], [82, 50, 34, 21]],
+ [[255, 154, 106, 65], [202, 122, 84, 52], [144, 87, 60, 37], [106, 64, 44, 27]],
+ [[322, 195, 134, 82], [255, 154, 106, 65], [178, 108, 74, 45], [139, 84, 58, 36]],
+ [[370, 224, 154, 95], [293, 178, 122, 75], [207, 125, 86, 53], [154, 93, 64, 39]],
+ [[461, 279, 192, 118], [365, 221, 152, 93], [259, 157, 108, 66], [202, 122, 84, 52]],
+ [[552, 335, 230, 141], [432, 262, 180, 111], [312, 189, 130, 80], [235, 143, 98, 60]],
+ [[652, 395, 271, 167], [513, 311, 213, 131], [364, 221, 151, 93], [288, 174, 119, 74]]
+ ];
+
+ static $QR_PATTERN_POSITION_TABLE = array(
+ [],
+ [6, 18],
+ [6, 22],
+ [6, 26],
+ [6, 30],
+ [6, 34],
+ [6, 22, 38],
+ [6, 24, 42],
+ [6, 26, 46],
+ [6, 28, 50],
+ [6, 30, 54],
+ [6, 32, 58],
+ [6, 34, 62],
+ [6, 26, 46, 66],
+ [6, 26, 48, 70],
+ [6, 26, 50, 74],
+ [6, 30, 54, 78],
+ [6, 30, 56, 82],
+ [6, 30, 58, 86],
+ [6, 34, 62, 90],
+ [6, 28, 50, 72, 94],
+ [6, 26, 50, 74, 98],
+ [6, 30, 54, 78, 102],
+ [6, 28, 54, 80, 106],
+ [6, 32, 58, 84, 110],
+ [6, 30, 58, 86, 114],
+ [6, 34, 62, 90, 118],
+ [6, 26, 50, 74, 98, 122],
+ [6, 30, 54, 78, 102, 126],
+ [6, 26, 52, 78, 104, 130],
+ [6, 30, 56, 82, 108, 134],
+ [6, 34, 60, 86, 112, 138],
+ [6, 30, 58, 86, 114, 142],
+ [6, 34, 62, 90, 118, 146],
+ [6, 30, 54, 78, 102, 126, 150],
+ [6, 24, 50, 76, 102, 128, 154],
+ [6, 28, 54, 80, 106, 132, 158],
+ [6, 32, 58, 84, 110, 136, 162],
+ [6, 26, 54, 82, 110, 138, 166],
+ [6, 30, 58, 86, 114, 142, 170]
+ );
+
+ static function getPatternPosition(int $typeNumber) : array
+ {
+ return self::$QR_PATTERN_POSITION_TABLE[$typeNumber - 1];
+ }
+
+ static function getMaxLength(int $typeNumber, int $mode, int $errorCorrectLevel) : int
+ {
+ $t = $typeNumber - 1;
+ $e = 0;
+ $m = 0;
+
+ switch ($errorCorrectLevel)
+ {
+ case QRCode::ERROR_CORRECT_LEVEL_L : $e = 0; break;
+ case QRCode::ERROR_CORRECT_LEVEL_M : $e = 1; break;
+ case QRCode::ERROR_CORRECT_LEVEL_Q : $e = 2; break;
+ case QRCode::ERROR_CORRECT_LEVEL_H : $e = 3; break;
+ default :
+ throw new \OutOfBoundsException("e:{$errorCorrectLevel}");
+ }
+
+ switch ($mode)
+ {
+ case QRData::MODE_NUMBER : $m = 0; break;
+ case QRData::MODE_ALPHA_NUM : $m = 1; break;
+ case QRData::MODE_8BIT_BYTE : $m = 2; break;
+ case QRData::MODE_KANJI : $m = 3; break;
+ default :
+ throw new \OutOfBoundsException("m:{$mode}");
+ }
+
+ return self::$QR_MAX_LENGTH[$t][$e][$m];
+ }
+
+ static function getErrorCorrectPolynomial(int $errorCorrectLength) : QRPolynomial
+ {
+ $a = new QRPolynomial([1]);
+ for ($i = 0; $i < $errorCorrectLength; ++$i) {
+ $a = $a->multiply(new QRPolynomial([1, QRMath::gexp($i)]));
+ }
+ return $a;
+ }
+
+ static function isMask(int $maskPattern, int $i, int $j) : bool
+ {
+ switch ($maskPattern)
+ {
+ case self::MASK_PATTERN000 : return ($i + $j) % 2 == 0;
+ case self::MASK_PATTERN001 : return $i % 2 == 0;
+ case self::MASK_PATTERN010 : return $j % 3 == 0;
+ case self::MASK_PATTERN011 : return ($i + $j) % 3 == 0;
+ case self::MASK_PATTERN100 : return (\floor($i / 2) + \floor($j / 3)) % 2 == 0;
+ case self::MASK_PATTERN101 : return ($i * $j) % 2 + ($i * $j) % 3 == 0;
+ case self::MASK_PATTERN110 : return (($i * $j) % 2 + ($i * $j) % 3) % 2 == 0;
+ case self::MASK_PATTERN111 : return (($i * $j) % 3 + ($i + $j) % 2) % 2 == 0;
+ default :
+ throw new \OutOfBoundsException("mask:{$maskPattern}");
+ }
+ }
+
+ static function getLostPoint(QRCode $qrCode) : int
+ {
+ $moduleCount = $qrCode->getModuleCount();
+ $lostPoint = 0;
+
+ // LEVEL1
+ for ($row = 0; $row < $moduleCount; ++$row) {
+ for ($col = 0; $col < $moduleCount; ++$col) {
+ $sameCount = 0;
+ $dark = $qrCode->isDark($row, $col);
+ for ($r = -1; $r <= 1; ++$r) {
+ if ($row + $r < 0 || $moduleCount <= $row + $r) {
+ continue;
+ }
+ for ($c = -1; $c <= 1; ++$c) {
+ if ($col + $c < 0 || $moduleCount <= $col + $c || ($r == 0 && $c == 0)) {
+ continue;
+ }
+ if ($dark == $qrCode->isDark($row + $r, $col + $c)) {
+ ++$sameCount;
+ }
+ }
+ }
+ if ($sameCount > 5) {
+ $lostPoint += (3 + $sameCount - 5);
+ }
+ }
+ }
+
+ // LEVEL2
+ for ($row = 0; $row < $moduleCount - 1; ++$row) {
+ for ($col = 0; $col < $moduleCount - 1; ++$col) {
+ $count = 0;
+ if ($qrCode->isDark($row, $col )) ++$count;
+ if ($qrCode->isDark($row + 1, $col )) ++$count;
+ if ($qrCode->isDark($row, $col + 1)) ++$count;
+ if ($qrCode->isDark($row + 1, $col + 1)) ++$count;
+ if ($count == 0 || $count == 4) {
+ $lostPoint += 3;
+ }
+ }
+ }
+
+ // LEVEL3
+ for ($row = 0; $row < $moduleCount; ++$row) {
+ for ($col = 0; $col < $moduleCount - 6; ++$col) {
+ if ($qrCode->isDark($row, $col)
+ && !$qrCode->isDark($row, $col + 1)
+ && $qrCode->isDark($row, $col + 2)
+ && $qrCode->isDark($row, $col + 3)
+ && $qrCode->isDark($row, $col + 4)
+ && !$qrCode->isDark($row, $col + 5)
+ && $qrCode->isDark($row, $col + 6)) {
+ $lostPoint += 40;
+ }
+ }
+ }
+ for ($col = 0; $col < $moduleCount; ++$col) {
+ for ($row = 0; $row < $moduleCount - 6; ++$row) {
+ if ($qrCode->isDark($row, $col)
+ && !$qrCode->isDark($row + 1, $col)
+ && $qrCode->isDark($row + 2, $col)
+ && $qrCode->isDark($row + 3, $col)
+ && $qrCode->isDark($row + 4, $col)
+ && !$qrCode->isDark($row + 5, $col)
+ && $qrCode->isDark($row + 6, $col)) {
+ $lostPoint += 40;
+ }
+ }
+ }
+
+ // LEVEL4
+ $darkCount = 0;
+ for ($col = 0; $col < $moduleCount; ++$col) {
+ for ($row = 0; $row < $moduleCount; ++$row) {
+ if ($qrCode->isDark($row, $col)) {
+ ++$darkCount;
+ }
+ }
+ }
+ $ratio = \abs(100 * $darkCount / $moduleCount / $moduleCount - 50) / 5;
+ $lostPoint += $ratio * 10;
+
+ return $lostPoint;
+ }
+
+ static function getBCHTypeInfo($data)
+ {
+ $d = $data << 10;
+ $g15 = static::getBCHDigit(self::G15);
+ while (true) {
+ $v = static::getBCHDigit($d) - $g15;
+ if ($v < 0) {
+ break;
+ }
+ $d ^= (self::G15 << $v);
+ }
+ return (($data << 10) | $d) ^ self::G15_MASK;
+ }
+
+ static function getBCHTypeNumber($data)
+ {
+ $d = $data << 12;
+ $g18 = static::getBCHDigit(self::G18);
+ while (true) {
+ $v = static::getBCHDigit($d) - $g18;
+ if ($v < 0) {
+ break;
+ }
+ $d ^= (self::G18 << $v);
+ }
+ return ($data << 12) | $d;
+ }
+
+ protected static function getBCHDigit($data) : int
+ {
+ $digit = 0;
+ while ($data != 0) {
+ ++$digit;
+ $data >>= 1;
+ }
+ return $digit;
+ }
+}
+
+//---------------------------------------------------------------
+// QRRSBlock
+//---------------------------------------------------------------
+
+class QRRSBlock {
+
+ protected $totalCount;
+ protected $dataCount;
+
+ static $QR_RS_BLOCK_TABLE = [
+
+ // L
+ // M
+ // Q
+ // H
+
+ // 1
+ [1, 26, 19],
+ [1, 26, 16],
+ [1, 26, 13],
+ [1, 26, 9],
+
+ // 2
+ [1, 44, 34],
+ [1, 44, 28],
+ [1, 44, 22],
+ [1, 44, 16],
+
+ // 3
+ [1, 70, 55],
+ [1, 70, 44],
+ [2, 35, 17],
+ [2, 35, 13],
+
+ // 4
+ [1, 100, 80],
+ [2, 50, 32],
+ [2, 50, 24],
+ [4, 25, 9],
+
+ // 5
+ [1, 134, 108],
+ [2, 67, 43],
+ [2, 33, 15, 2, 34, 16],
+ [2, 33, 11, 2, 34, 12],
+
+ // 6
+ [2, 86, 68],
+ [4, 43, 27],
+ [4, 43, 19],
+ [4, 43, 15],
+
+ // 7
+ [2, 98, 78],
+ [4, 49, 31],
+ [2, 32, 14, 4, 33, 15],
+ [4, 39, 13, 1, 40, 14],
+
+ // 8
+ [2, 121, 97],
+ [2, 60, 38, 2, 61, 39],
+ [4, 40, 18, 2, 41, 19],
+ [4, 40, 14, 2, 41, 15],
+
+ // 9
+ [2, 146, 116],
+ [3, 58, 36, 2, 59, 37],
+ [4, 36, 16, 4, 37, 17],
+ [4, 36, 12, 4, 37, 13],
+
+ // 10
+ [2, 86, 68, 2, 87, 69],
+ [4, 69, 43, 1, 70, 44],
+ [6, 43, 19, 2, 44, 20],
+ [6, 43, 15, 2, 44, 16],
+
+ // 11
+ [4, 101, 81],
+ [1, 80, 50, 4, 81, 51],
+ [4, 50, 22, 4, 51, 23],
+ [3, 36, 12, 8, 37, 13],
+
+ // 12
+ [2, 116, 92, 2, 117, 93],
+ [6, 58, 36, 2, 59, 37],
+ [4, 46, 20, 6, 47, 21],
+ [7, 42, 14, 4, 43, 15],
+
+ // 13
+ [4, 133, 107],
+ [8, 59, 37, 1, 60, 38],
+ [8, 44, 20, 4, 45, 21],
+ [12, 33, 11, 4, 34, 12],
+
+ // 14
+ [3, 145, 115, 1, 146, 116],
+ [4, 64, 40, 5, 65, 41],
+ [11, 36, 16, 5, 37, 17],
+ [11, 36, 12, 5, 37, 13],
+
+ // 15
+ [5, 109, 87, 1, 110, 88],
+ [5, 65, 41, 5, 66, 42],
+ [5, 54, 24, 7, 55, 25],
+ [11, 36, 12, 7, 37, 13],
+
+ // 16
+ [5, 122, 98, 1, 123, 99],
+ [7, 73, 45, 3, 74, 46],
+ [15, 43, 19, 2, 44, 20],
+ [3, 45, 15, 13, 46, 16],
+
+ // 17
+ [1, 135, 107, 5, 136, 108],
+ [10, 74, 46, 1, 75, 47],
+ [1, 50, 22, 15, 51, 23],
+ [2, 42, 14, 17, 43, 15],
+
+ // 18
+ [5, 150, 120, 1, 151, 121],
+ [9, 69, 43, 4, 70, 44],
+ [17, 50, 22, 1, 51, 23],
+ [2, 42, 14, 19, 43, 15],
+
+ // 19
+ [3, 141, 113, 4, 142, 114],
+ [3, 70, 44, 11, 71, 45],
+ [17, 47, 21, 4, 48, 22],
+ [9, 39, 13, 16, 40, 14],
+
+ // 20
+ [3, 135, 107, 5, 136, 108],
+ [3, 67, 41, 13, 68, 42],
+ [15, 54, 24, 5, 55, 25],
+ [15, 43, 15, 10, 44, 16],
+
+ // 21
+ [4, 144, 116, 4, 145, 117],
+ [17, 68, 42],
+ [17, 50, 22, 6, 51, 23],
+ [19, 46, 16, 6, 47, 17],
+
+ // 22
+ [2, 139, 111, 7, 140, 112],
+ [17, 74, 46],
+ [7, 54, 24, 16, 55, 25],
+ [34, 37, 13],
+
+ // 23
+ [4, 151, 121, 5, 152, 122],
+ [4, 75, 47, 14, 76, 48],
+ [11, 54, 24, 14, 55, 25],
+ [16, 45, 15, 14, 46, 16],
+
+ // 24
+ [6, 147, 117, 4, 148, 118],
+ [6, 73, 45, 14, 74, 46],
+ [11, 54, 24, 16, 55, 25],
+ [30, 46, 16, 2, 47, 17],
+
+ // 25
+ [8, 132, 106, 4, 133, 107],
+ [8, 75, 47, 13, 76, 48],
+ [7, 54, 24, 22, 55, 25],
+ [22, 45, 15, 13, 46, 16],
+
+ // 26
+ [10, 142, 114, 2, 143, 115],
+ [19, 74, 46, 4, 75, 47],
+ [28, 50, 22, 6, 51, 23],
+ [33, 46, 16, 4, 47, 17],
+
+ // 27
+ [8, 152, 122, 4, 153, 123],
+ [22, 73, 45, 3, 74, 46],
+ [8, 53, 23, 26, 54, 24],
+ [12, 45, 15, 28, 46, 16],
+
+ // 28
+ [3, 147, 117, 10, 148, 118],
+ [3, 73, 45, 23, 74, 46],
+ [4, 54, 24, 31, 55, 25],
+ [11, 45, 15, 31, 46, 16],
+
+ // 29
+ [7, 146, 116, 7, 147, 117],
+ [21, 73, 45, 7, 74, 46],
+ [1, 53, 23, 37, 54, 24],
+ [19, 45, 15, 26, 46, 16],
+
+ // 30
+ [5, 145, 115, 10, 146, 116],
+ [19, 75, 47, 10, 76, 48],
+ [15, 54, 24, 25, 55, 25],
+ [23, 45, 15, 25, 46, 16],
+
+ // 31
+ [13, 145, 115, 3, 146, 116],
+ [2, 74, 46, 29, 75, 47],
+ [42, 54, 24, 1, 55, 25],
+ [23, 45, 15, 28, 46, 16],
+
+ // 32
+ [17, 145, 115],
+ [10, 74, 46, 23, 75, 47],
+ [10, 54, 24, 35, 55, 25],
+ [19, 45, 15, 35, 46, 16],
+
+ // 33
+ [17, 145, 115, 1, 146, 116],
+ [14, 74, 46, 21, 75, 47],
+ [29, 54, 24, 19, 55, 25],
+ [11, 45, 15, 46, 46, 16],
+
+ // 34
+ [13, 145, 115, 6, 146, 116],
+ [14, 74, 46, 23, 75, 47],
+ [44, 54, 24, 7, 55, 25],
+ [59, 46, 16, 1, 47, 17],
+
+ // 35
+ [12, 151, 121, 7, 152, 122],
+ [12, 75, 47, 26, 76, 48],
+ [39, 54, 24, 14, 55, 25],
+ [22, 45, 15, 41, 46, 16],
+
+ // 36
+ [6, 151, 121, 14, 152, 122],
+ [6, 75, 47, 34, 76, 48],
+ [46, 54, 24, 10, 55, 25],
+ [2, 45, 15, 64, 46, 16],
+
+ // 37
+ [17, 152, 122, 4, 153, 123],
+ [29, 74, 46, 14, 75, 47],
+ [49, 54, 24, 10, 55, 25],
+ [24, 45, 15, 46, 46, 16],
+
+ // 38
+ [4, 152, 122, 18, 153, 123],
+ [13, 74, 46, 32, 75, 47],
+ [48, 54, 24, 14, 55, 25],
+ [42, 45, 15, 32, 46, 16],
+
+ // 39
+ [20, 147, 117, 4, 148, 118],
+ [40, 75, 47, 7, 76, 48],
+ [43, 54, 24, 22, 55, 25],
+ [10, 45, 15, 67, 46, 16],
+
+ // 40
+ [19, 148, 118, 6, 149, 119],
+ [18, 75, 47, 31, 76, 48],
+ [34, 54, 24, 34, 55, 25],
+ [20, 45, 15, 61, 46, 16]
+ ];
+
+ public function __construct(int $totalCount, int $dataCount)
+ {
+ $this->totalCount = $totalCount;
+ $this->dataCount = $dataCount;
+ }
+
+ public function getDataCount() : int
+ {
+ return $this->dataCount;
+ }
+
+ public function getTotalCount() : int
+ {
+ return $this->totalCount;
+ }
+
+ static function getRSBlocks(int $typeNumber, int $errorCorrectLevel) : array
+ {
+ $rsBlock = static::getRsBlockTable($typeNumber, $errorCorrectLevel);
+ $length = \count($rsBlock) / 3;
+
+ $list = [];
+
+ for ($i = 0; $i < $length; ++$i) {
+
+ $count = $rsBlock[$i * 3 + 0];
+ $totalCount = $rsBlock[$i * 3 + 1];
+ $dataCount = $rsBlock[$i * 3 + 2];
+
+ for ($j = 0; $j < $count; ++$j) {
+ $list[] = new static($totalCount, $dataCount);
+ }
+ }
+
+ return $list;
+ }
+
+ static function getRsBlockTable(int $typeNumber, int $errorCorrectLevel) : array
+ {
+ switch ($errorCorrectLevel)
+ {
+ case QRCode::ERROR_CORRECT_LEVEL_L : return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 0];
+ case QRCode::ERROR_CORRECT_LEVEL_M : return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 1];
+ case QRCode::ERROR_CORRECT_LEVEL_Q : return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 2];
+ case QRCode::ERROR_CORRECT_LEVEL_H : return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 3];
+ default :
+ throw new \OutOfBoundsException("tn:{$typeNumber}/ecl:{$errorCorrectLevel}");
+ }
+ }
+}
+
+//---------------------------------------------------------------
+// QRData
+//---------------------------------------------------------------
+
+class QRData
+{
+ const
+ MODE_NUMBER = 1,
+ MODE_ALPHA_NUM = 2,
+ MODE_8BIT_BYTE = 4,
+ MODE_KANJI = 8;
+
+ protected
+ $data,
+ $mode;
+
+ public function __construct($data, int $mode = 0)
+ {
+ $this->data = $data;
+ $this->mode = $mode ?: static::detectMode($data);
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getLength() : int
+ {
+ return (self::MODE_KANJI == $this->mode)
+ ? \floor(\strlen($this->data) / 2)
+ : \strlen($this->data);
+ }
+
+ public function getMode() : int
+ {
+ return $this->mode;
+ }
+
+ public function getLengthInBits($type) : int
+ {
+ if (1 > $type || 40 < $type) {
+ throw new \OutOfBoundsException("Invalid type: {$type}");
+ }
+ switch ($this->mode)
+ {
+ case self::MODE_NUMBER:
+ if ($type < 10) {
+ return 10;
+ }
+ if ($type < 27) {
+ return 12;
+ }
+ return 14;
+
+ case self::MODE_ALPHA_NUM:
+ if ($type < 10) {
+ return 9;
+ }
+ if ($type < 27) {
+ return 11;
+ }
+ return 13;
+
+ case self::MODE_8BIT_BYTE:
+ if ($type < 10) {
+ return 8;
+ }
+ if ($type < 27) {
+ return 16;
+ }
+ return 16;
+
+ case self::MODE_KANJI:
+ if ($type < 10) {
+ return 8;
+ }
+ if ($type < 27) {
+ return 10;
+ }
+ return 12;
+ }
+ }
+
+ public function write(&$buffer) : void
+ {
+ $i = 0;
+ $data = $this->data;
+ $length = \strlen($data);
+ switch ($this->mode)
+ {
+ case self::MODE_NUMBER:
+ while ($i + 2 < $length) {
+ $num = static::parseInt(\substr($data, $i, 3));
+ $buffer->put($num, 10);
+ $i += 3;
+ }
+ if ($i < $length) {
+ if ($length - $i == 1) {
+ $num = static::parseInt(\substr($data, $i, $i + 1));
+ $buffer->put($num, 4);
+ } else if ($length - $i == 2) {
+ $num = static::parseInt(\substr($data, $i, $i + 2));
+ $buffer->put($num, 7);
+ }
+ }
+ break;
+
+ case self::MODE_ALPHA_NUM:
+ while ($i + 1 < $length) {
+ $buffer->put(static::getCode(\ord($data[$i])) * 45 + static::getCode(\ord($data[$i + 1])), 11);
+ $i += 2;
+ }
+ if ($i < $length) {
+ $buffer->put(static::getCode(\ord($data[$i])), 6);
+ }
+ break;
+
+ case self::MODE_8BIT_BYTE:
+ for (; $i < $length; ++$i) {
+ $buffer->put(\ord($data[$i]), 8);
+ }
+ break;
+
+ case self::MODE_KANJI:
+ while ($i + 1 < $length) {
+ $c = ((0xff & \ord($data[$i])) << 8) | (0xff & \ord($data[$i + 1]));
+ if (0x8140 <= $c && $c <= 0x9FFC) {
+ $c -= 0x8140;
+ } else if (0xE040 <= $c && $c <= 0xEBBF) {
+ $c -= 0xC140;
+ } else {
+ throw new \OutOfBoundsException("illegal char at " . ($i + 1) . "/{$c}");
+ }
+ $c = (($c >> 8) & 0xff) * 0xC0 + ($c & 0xff);
+ $buffer->put($c, 13);
+ $i += 2;
+ }
+ if ($i < $length) {
+ throw new \OutOfBoundsException("illegal char at " . ($i + 1));
+ }
+ break;
+ }
+ }
+
+ protected static function parseInt($s) : int
+ {
+ if (!\ctype_digit((string)$s)) {
+ throw new \OutOfBoundsException("Not numeric: {$s}");
+ }
+ return (int) $s;
+ }
+
+ protected static function getCode($c) : int
+ {
+ // 0-9
+ if (0x30 <= $c && $c <= 0x39) {
+ return $c - 0x30;
+ }
+ // A-Z
+ if (0x41 <= $c && $c <= 0x5A) {
+ return $c - 0x41 + 10;
+ }
+ switch ($c)
+ {
+ case 0x20: return 36; // ' '
+ case 0x24: return 37; // $
+ case 0x25: return 38; // %
+ case 0x2A: return 39; // *
+ case 0x2B: return 40; // +
+ case 0x2D: return 41; // -
+ case 0x2E: return 42; // .
+ case 0x2F: return 43; // /
+ case 0x3A: return 44; // :
+ default :
+ throw new \OutOfBoundsException("illegal char: {$c}");
+ }
+ }
+
+ protected static function detectMode($s) : int
+ {
+ if (\ctype_digit((string)$s)) {
+ return self::MODE_NUMBER;
+ }
+ if (\preg_match('#^[0-9A-Z \\$\\%\\*\\+\\-\\.\\/\\:]*$#D', $s)) {
+ return self::MODE_ALPHA_NUM;
+ }
+ return static::isKanji($s) ? self::MODE_KANJI : self::MODE_8BIT_BYTE;
+ }
+
+ protected static function isKanji(string $s) : bool
+ {
+ $data = $s;
+ $length = \strlen($data);
+ $i = 0;
+ while ($i + 1 < $length) {
+ $c = ((0xff & \ord($data[$i])) << 8) | (0xff & \ord($data[$i + 1]));
+ if (!(0x8140 <= $c && $c <= 0x9FFC) && !(0xE040 <= $c && $c <= 0xEBBF)) {
+ return false;
+ }
+ $i += 2;
+ }
+ return !($i < $length);
+ }
+}
+
+//---------------------------------------------------------------
+// QRMath
+//---------------------------------------------------------------
+
+abstract class QRMath
+{
+ protected static
+ $EXP_TABLE = [1,2,4,8,16,32,64,128,29,58,116,232,205,135,19,38,76,152,45,90,180,117,234,201,143,3,6,12,24,48,96,192,157,39,78,156,37,74,148,53,106,212,181,119,238,193,159,35,70,140,5,10,20,40,80,160,93,186,105,210,185,111,222,161,95,190,97,194,153,47,94,188,101,202,137,15,30,60,120,240,253,231,211,187,107,214,177,127,254,225,223,163,91,182,113,226,217,175,67,134,17,34,68,136,13,26,52,104,208,189,103,206,129,31,62,124,248,237,199,147,59,118,236,197,151,51,102,204,133,23,46,92,184,109,218,169,79,158,33,66,132,21,42,84,168,77,154,41,82,164,85,170,73,146,57,114,228,213,183,115,230,209,191,99,198,145,63,126,252,229,215,179,123,246,241,255,227,219,171,75,150,49,98,196,149,55,110,220,165,87,174,65,130,25,50,100,200,141,7,14,28,56,112,224,221,167,83,166,81,162,89,178,121,242,249,239,195,155,43,86,172,69,138,9,18,36,72,144,61,122,244,245,247,243,251,235,203,139,11,22,44,88,176,125,250,233,207,131,27,54,108,216,173,71,142,1],
+ $LOG_TABLE = [0,0,1,25,2,50,26,198,3,223,51,238,27,104,199,75,4,100,224,14,52,141,239,129,28,193,105,248,200,8,76,113,5,138,101,47,225,36,15,33,53,147,142,218,240,18,130,69,29,181,194,125,106,39,249,185,201,154,9,120,77,228,114,166,6,191,139,98,102,221,48,253,226,152,37,179,16,145,34,136,54,208,148,206,143,150,219,189,241,210,19,92,131,56,70,64,30,66,182,163,195,72,126,110,107,58,40,84,250,133,186,61,202,94,155,159,10,21,121,43,78,212,229,172,115,243,167,87,7,112,192,247,140,128,99,13,103,74,222,237,49,197,254,24,227,165,153,119,38,184,180,124,17,68,146,217,35,32,137,46,55,63,209,91,149,188,207,205,144,135,151,178,220,252,190,97,242,86,211,171,20,42,93,158,132,60,57,83,71,109,65,162,31,45,67,216,183,123,164,118,196,23,73,236,127,12,111,246,108,161,59,82,41,157,85,170,251,96,134,177,187,204,62,90,203,89,95,176,156,169,160,81,11,245,22,235,122,117,44,215,79,174,213,233,230,231,173,232,116,214,244,234,168,80,88,175];
+
+ static function glog(int $n) : int
+ {
+ if ($n < 1) {
+ throw new \OutOfBoundsException("log({$n})");
+ }
+ return self::$LOG_TABLE[$n];
+ }
+
+ static function gexp(int $n) : int
+ {
+ if ($n < 0) {
+ $n = $n % 255 + 255;
+ }
+ if ($n > 255) {
+ $n = $n % 255;
+ }
+ return self::$EXP_TABLE[$n];
+ }
+}
+
+//---------------------------------------------------------------
+// QRPolynomial
+//---------------------------------------------------------------
+
+class QRPolynomial {
+
+ protected $num;
+
+ public function __construct($num, $shift = 0)
+ {
+ $offset = 0;
+ $limit = \count($num);
+ while ($offset < $limit && $num[$offset] == 0) {
+ ++$offset;
+ }
+ $this->num = \array_fill(0, $limit - $offset + $shift, 0);
+ for ($i = 0; $i < $limit - $offset; ++$i) {
+ $this->num[$i] = $num[$i + $offset];
+ }
+ }
+
+ public function get(int $index)
+ {
+ return $this->num[$index];
+ }
+
+ public function getLength() : int
+ {
+ return \count($this->num);
+ }
+
+ public function multiply($e) : self
+ {
+ $tl = $this->getLength();
+ $el = $e->getLength();
+ $num = \array_fill(0, $tl + $el - 1, 0);
+ for ($i = 0; $i < $tl; ++$i) {
+ $vi = QRMath::glog($this->get($i));
+ for ($j = 0; $j < $el; ++$j) {
+ $num[$i + $j] ^= QRMath::gexp($vi + QRMath::glog($e->get($j)));
+ }
+ }
+ return new static($num);
+ }
+
+ public function mod($e) : self
+ {
+ $tl = $this->getLength();
+ $el = $e->getLength();
+ if ($tl < $el) {
+ return $this;
+ }
+ $ratio = QRMath::glog($this->get(0)) - QRMath::glog($e->get(0));
+ $num = [];
+ for ($i = 0; $i < $tl; ++$i) {
+ $num[$i] = $this->num[$i];
+ if ($i < $el) {
+ $num[$i] ^= QRMath::gexp(QRMath::glog($e->get($i)) + $ratio);
+ }
+ }
+ $newPolynomial = new static($num);
+ return $newPolynomial->mod($e);
+ }
+}
+
+//---------------------------------------------------------------
+// QRBitBuffer
+//---------------------------------------------------------------
+
+class QRBitBuffer {
+
+ protected $buffer = [];
+ protected $length = 0;
+
+ public function getBuffer() : array
+ {
+ return $this->buffer;
+ }
+
+ public function getLengthInBits() : int
+ {
+ return $this->length;
+ }
+
+ public function get(int $index) : bool
+ {
+ $bufIndex = (int)\floor($index / 8);
+ return (($this->buffer[$bufIndex] >> (7 - $index % 8)) & 1) == 1;
+ }
+
+ public function put($num, int $length) : void
+ {
+ for ($i = 0; $i < $length; ++$i) {
+ $this->putBit((($num >> ($length - $i - 1)) & 1) == 1);
+ }
+ }
+
+ public function putBit($bit) : void
+ {
+ $bufIndex = (int)\floor($this->length / 8);
+ if (\count($this->buffer) <= $bufIndex) {
+ $this->buffer[] = 0;
+ }
+ if ($bit) {
+ $this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8));
+ }
+ ++$this->length;
+ }
+}