HEX
Server: LiteSpeed
System: Linux php-prod-1.spaceapp.ru 5.15.0-157-generic #167-Ubuntu SMP Wed Sep 17 21:35:53 UTC 2025 x86_64
User: sport3497 (1034)
PHP: 8.1.33
Disabled: NONE
Upload Files
File: //usr/local/CyberCP/public/snappymail/snappymail/v/2.38.2/app/libraries/snappymail/qrcode.php
<?php
//---------------------------------------------------------------
// QRCode
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
//   http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
//   http://www.denso-wave.com/qrcode/faqpatent-e.html
//
// Modified for PHP 7 and outputs unicode:
//	- QRCode->__toString()
//---------------------------------------------------------------------

namespace SnappyMail;

class QRCode implements \Stringable
{

	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 int $typeNumber = 1;

	protected array $modules;

	protected int $moduleCount;

	protected int $errorCorrectLevel = self::ERROR_CORRECT_LEVEL_H;

	protected array $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(string $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(array &$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(QRBitBuffer $buffer, array &$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, array $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(string $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;
				}
			}
		}

		return $lostPoint + \intval(\abs(100 * $darkCount / $moduleCount / $moduleCount - 50) * 10 / 5);
	}

	static function getBCHTypeInfo(int $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(int $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(int $data) : int
	{
		$digit = 0;
		while ($data != 0) {
			++$digit;
			$data >>= 1;
		}
		return $digit;
	}
}

//---------------------------------------------------------------
// QRRSBlock
//---------------------------------------------------------------

class QRRSBlock {

	protected int $totalCount;

	protected int $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 string $data;

	protected int $mode;

	public function __construct(string $data, int $mode = 0)
	{
		$this->data = $data;
		$this->mode = $mode ?: static::detectMode($data);
	}

	public function getData() : string
	{
		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(int $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(QRBitBuffer $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 array $num;

	public function __construct(array $num, int $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(self $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(self $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(bool $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;
	}
}