From 7ea7b3c85af0d73a9fd79b8053c9c9fbedac11e6 Mon Sep 17 00:00:00 2001 From: smiley Date: Thu, 23 Nov 2017 02:28:42 +0100 Subject: [PATCH] :sparkles: --- composer.json | 4 +- public/index.html | 164 ++++ public/qrcode.php | 90 +++ src/Container.php | 66 -- src/Data/AlphaNum.php | 54 +- src/Data/Byte.php | 18 +- src/Data/Kanji.php | 31 +- src/Data/MaskPatternTester.php | 215 ++++++ src/Data/Number.php | 28 +- src/Data/QRDataAbstract.php | 318 +++++++- src/Data/QRDataInterface.php | 164 +++- src/Data/QRMatrix.php | 563 ++++++++++++++ src/{ => Helpers}/BitBuffer.php | 17 +- src/Helpers/Polynomial.php | 182 +++++ src/Output/QRImage.php | 114 ++- src/Output/QRImageOptions.php | 101 --- src/Output/QRMarkup.php | 86 +-- src/Output/QRMarkupOptions.php | 85 --- src/Output/QROutputAbstract.php | 55 +- src/Output/QROutputInterface.php | 8 - src/Output/QROutputOptionsAbstract.php | 48 -- src/Output/QRString.php | 11 +- src/Output/QRStringOptions.php | 45 -- src/Polynomial.php | 158 ---- src/QRCode.php | 312 ++++++-- src/QRDataGenerator.php | 989 ------------------------- src/QROptions.php | 234 +++++- tests/{ => Data}/BitBufferTest.php | 23 +- tests/Data/DataTest.php | 120 --- tests/{ => Data}/PolynomialTest.php | 17 +- tests/Data/QRMatrixTest.php | 49 ++ tests/Output/ImageTest.php | 59 -- tests/Output/MarkupTest.php | 87 --- tests/Output/OutputTestAbstract.php | 56 -- tests/Output/StringTest.php | 46 -- tests/Output/image/img1.gif.uri | 1 - tests/Output/image/img1.jpg.uri | 1 - tests/Output/image/img1.png.uri | 1 - tests/Output/image/img2.gif.uri | 1 - tests/Output/image/img2.jpg.uri | 1 - tests/Output/image/img2.png.uri | 1 - tests/Output/markup/str1.html | 21 - tests/Output/markup/str1.svg | 124 ---- tests/Output/markup/str2.html | 21 - tests/Output/markup/str2.svg | 366 --------- tests/Output/markup/str3.html | 37 - tests/Output/markup/str4.html | 37 - tests/Output/string/str1.json | 1 - tests/Output/string/str1.txt | 21 - tests/Output/string/str2.json | 1 - tests/Output/string/str2.txt | 37 - tests/Output/string/str3.txt | 49 -- tests/QRCodeTest.php | 113 +-- tests/QRDataGeneratorTest.php | 172 ----- tests/QRTestAbstract.php | 74 ++ 55 files changed, 2453 insertions(+), 3244 deletions(-) create mode 100644 public/index.html create mode 100644 public/qrcode.php delete mode 100644 src/Container.php create mode 100644 src/Data/MaskPatternTester.php create mode 100644 src/Data/QRMatrix.php rename src/{ => Helpers}/BitBuffer.php (74%) create mode 100644 src/Helpers/Polynomial.php delete mode 100644 src/Output/QRImageOptions.php delete mode 100644 src/Output/QRMarkupOptions.php delete mode 100644 src/Output/QROutputOptionsAbstract.php delete mode 100644 src/Output/QRStringOptions.php delete mode 100644 src/Polynomial.php delete mode 100644 src/QRDataGenerator.php rename tests/{ => Data}/BitBufferTest.php (63%) delete mode 100644 tests/Data/DataTest.php rename tests/{ => Data}/PolynomialTest.php (58%) create mode 100644 tests/Data/QRMatrixTest.php delete mode 100644 tests/Output/ImageTest.php delete mode 100644 tests/Output/MarkupTest.php delete mode 100644 tests/Output/OutputTestAbstract.php delete mode 100644 tests/Output/StringTest.php delete mode 100644 tests/Output/image/img1.gif.uri delete mode 100644 tests/Output/image/img1.jpg.uri delete mode 100644 tests/Output/image/img1.png.uri delete mode 100644 tests/Output/image/img2.gif.uri delete mode 100644 tests/Output/image/img2.jpg.uri delete mode 100644 tests/Output/image/img2.png.uri delete mode 100644 tests/Output/markup/str1.html delete mode 100644 tests/Output/markup/str1.svg delete mode 100644 tests/Output/markup/str2.html delete mode 100644 tests/Output/markup/str2.svg delete mode 100644 tests/Output/markup/str3.html delete mode 100644 tests/Output/markup/str4.html delete mode 100644 tests/Output/string/str1.json delete mode 100644 tests/Output/string/str1.txt delete mode 100644 tests/Output/string/str2.json delete mode 100644 tests/Output/string/str2.txt delete mode 100644 tests/Output/string/str3.txt delete mode 100644 tests/QRDataGeneratorTest.php create mode 100644 tests/QRTestAbstract.php diff --git a/composer.json b/composer.json index cc3bbc586..cf7997c6b 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ } ], "require": { - "php": ">=7.0.3" + "php": ">=7.0.3", + "chillerlan/php-traits": "1.0.*" }, "require-dev": { "chillerlan/php-googleauth": "1.1.*", @@ -34,6 +35,7 @@ }, "autoload-dev": { "psr-4": { + "chillerlan\\QRCodePublic\\": "public/", "chillerlan\\QRCodeTest\\": "tests/" } } diff --git a/public/index.html b/public/index.html new file mode 100644 index 000000000..f7cf8dc96 --- /dev/null +++ b/public/index.html @@ -0,0 +1,164 @@ + + + + + + QR Code Generator + + + + +
+ +

+ + + + + + + + + + +
+ + + + + + +
+ + + + + + + +
Finder
+ + + +
Alignment
+ + + +
Timing
+ + + +
Format
+ + + +
Version
+ + + +
Data
+ + + +
Dark Module
+ + + +
Separator
+ + + +
Quiet Zone
+ + + +
+ +
+
+ +
ZXing Barcode Scanner
+ + + + + + \ No newline at end of file diff --git a/public/qrcode.php b/public/qrcode.php new file mode 100644 index 000000000..480fdfb91 --- /dev/null +++ b/public/qrcode.php @@ -0,0 +1,90 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodePublic; + +use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\QROptions; + +require_once '../vendor/autoload.php'; + +try{ + + $moduleValues = [ + // finder + 1536 => $_POST['m_finder_dark'], + 6 => $_POST['m_finder_light'], + // alignment + 2560 => $_POST['m_alignment_dark'], + 10 => $_POST['m_alignment_light'], + // timing + 3072 => $_POST['m_timing_dark'], + 12 => $_POST['m_timing_light'], + // format + 3584 => $_POST['m_format_dark'], + 14 => $_POST['m_format_light'], + // version + 4096 => $_POST['m_version_dark'], + 16 => $_POST['m_version_light'], + // data + 1024 => $_POST['m_data_dark'], + 4 => $_POST['m_data_light'], + // darkmodule + 512 => $_POST['m_darkmodule_dark'], + // separator + 8 => $_POST['m_separator_light'], + // quietzone + 18 => $_POST['m_quietzone_light'], + ]; + + $moduleValues = array_map(function($v){ + #var_dump(array_map('hexdec', unpack('a2r/a2g/a2b','ff00ff'))); + + if(preg_match('/[a-f\d]{6}/i', $v) === 1){ + return '#'.$v; + } + + return strpos($v, '_dark') !== false + ? '#111111' + : '#eeeeee'; + + }, $moduleValues); + + + $ecc = in_array($_POST['ecc'], ['L', 'M', 'Q', 'H'], true) ? $_POST['ecc'] : 'L'; + + $qro = new QROptions; + + $qro->version = (int)$_POST['version']; + $qro->eccLevel = constant('chillerlan\\QRCode\\QRCode::ECC_'.$ecc); + $qro->moduleValues = $moduleValues; + $qro->outputType = $_POST['output_type']; + $qro->maskPattern = (int)$_POST['maskpattern']; + $qro->addQuietzone = isset($_POST['quietzone']); + $qro->quietzoneSize = (int)$_POST['quietzonesize']; + $qro->scale = (int)$_POST['scale']; + + $qrcode = (new QRCode($qro))->render($_POST['inputstring']); + + send_response(['qrcode' => $qrcode]); +} +// Pokรฉmon exception handler +catch(\Exception $e){ + header('HTTP/1.1 500 Internal Server Error'); + send_response(['error' => $e->getMessage()]); +} + +/** + * @param array $response + */ +function send_response(array $response){ + header('Content-type: application/json;charset=utf-8;'); + echo json_encode($response); + exit; +} diff --git a/src/Container.php b/src/Container.php deleted file mode 100644 index fa6ed3102..000000000 --- a/src/Container.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode; - -/** - * a generic container with getter and setter - * @codeCoverageIgnore - */ -trait Container{ - - /** - * Boa constructor. - * - * @param array $properties - */ - public function __construct(array $properties = []){ - - foreach($properties as $key => $value){ - $this->__set($key, $value); - } - - } - - /** - * David Getter - * - * @param string $property - * - * @return mixed - */ - public function __get(string $property){ - - if(property_exists($this, $property)){ - return $this->{$property}; - } - - return false; - } - - /** - * Jet-setter - * - * @param string $property - * @param mixed $value - * - * @return void - */ - public function __set(string $property, $value){ - - if(property_exists($this, $property)){ - $this->{$property} = $value; - } - - } - -} diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index 333da7154..70dcf01aa 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -12,29 +12,26 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCode; /** - * + * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : */ class AlphaNum extends QRDataAbstract{ const CHAR_MAP = [ - 36 => ' ', - 37 => '$', - 38 => '%', - 39 => '*', - 40 => '+', - 41 => '-', - 42 => '.', - 43 => '/', - 44 => ':', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', + '+', '-', '.', '/', ':', ]; /** * @var int */ - public $mode = self::MODE_ALPHANUM; + protected $datamode = QRCode::DATA_ALPHANUM; /** * @var array @@ -42,20 +39,16 @@ class AlphaNum extends QRDataAbstract{ protected $lengthBits = [9, 11, 13]; /** - * @param \chillerlan\QRCode\BitBuffer $buffer - * - * @return void + * @inheritdoc */ - public function write(BitBuffer &$buffer){ - $i = 0; + protected function write(string $data){ - while($i + 1 < $this->dataLength){ - $buffer->put($this->getCharCode($this->data[$i]) * 45 + $this->getCharCode($this->data[$i + 1]), 11); - $i += 2; + for($i = 0; $i + 1 < $this->strlen; $i += 2){ + $this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11); } - if($i < $this->dataLength){ - $buffer->put($this->getCharCode($this->data[$i]), 6); + if($i < $this->strlen){ + $this->bitBuffer->put($this->getCharCode($data[$i]), 6); } } @@ -66,21 +59,14 @@ class AlphaNum extends QRDataAbstract{ * @return int * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - private static function getCharCode(string $chr):int { - $chr = ord($chr); + protected function getCharCode(string $chr):int { + $i = array_search($chr, self::CHAR_MAP); - switch(true){ - case ord('0') <= $chr && $chr <= ord('9'): return $chr - ord('0'); - case ord('A') <= $chr && $chr <= ord('Z'): return $chr - ord('A') + 10; - default: - foreach(self::CHAR_MAP as $i => $c){ - if(ord($c) === $chr){ - return $i; - } - } + if($i !== false){ + return $i; } - throw new QRCodeDataException('illegal char: '.$chr); + throw new QRCodeDataException('illegal char: "'.$chr.'" ['.ord($chr).']'); } } diff --git a/src/Data/Byte.php b/src/Data/Byte.php index aa60f4405..e7cbfa448 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -12,17 +12,17 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCode; /** - * + * Byte mode, ISO-8859-1 or UTF-8 */ class Byte extends QRDataAbstract{ /** * @var int */ - public $mode = self::MODE_BYTE; + protected $datamode = QRCode::DATA_BYTE; /** * @var array @@ -30,16 +30,16 @@ class Byte extends QRDataAbstract{ protected $lengthBits = [8, 16, 16]; /** - * @param \chillerlan\QRCode\BitBuffer $buffer - * - * @return void + * @inheritdoc */ - public function write(BitBuffer &$buffer){ + protected function write(string $data){ $i = 0; - while($i < $this->dataLength){ - $buffer->put(ord($this->data[$i]), 8); + + while($i < $this->strlen){ + $this->bitBuffer->put(ord($data[$i]), 8); $i++; } + } } diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index acdb10811..8cad909e4 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -12,17 +12,17 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCode; /** - * + * Kanji mode: double-byte characters from the Shift JIS character set */ class Kanji extends QRDataAbstract{ /** * @var int */ - public $mode = self::MODE_KANJI; + protected $datamode = QRCode::DATA_KANJI; /** * @var array @@ -30,32 +30,39 @@ class Kanji extends QRDataAbstract{ protected $lengthBits = [8, 10, 12]; /** - * @param \chillerlan\QRCode\BitBuffer $buffer + * @inheritdoc + */ + protected function getLength(string $data):int{ + return mb_strlen($data, 'SJIS'); + } + + /** + * @param string $data * * @return void * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public function write(BitBuffer &$buffer){ + protected function write(string $data){ + $len = strlen($data); - $i = 0; - while($i + 1 < $this->dataLength){ - $c = ((0xff&ord($this->data[$i])) << 8)|(0xff&ord($this->data[$i + 1])); + for($i = 0; $i + 1 < $len; $i += 2){ + $c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1])); if(0x8140 <= $c && $c <= 0x9FFC){ $c -= 0x8140; } - else if(0xE040 <= $c && $c <= 0xEBBF){ + elseif(0xE040 <= $c && $c <= 0xEBBF){ $c -= 0xC140; } else{ throw new QRCodeDataException('illegal char at '.($i + 1).' ('.$c.')'); } - $buffer->put((($c >> 8)&0xff) * 0xC0 + ($c&0xff), 13); - $i += 2; + $this->bitBuffer->put((($c >> 8) & 0xff) * 0xC0 + ($c & 0xff), 13); + } - if($i < $this->dataLength){ + if($i < $len){ throw new QRCodeDataException('illegal char at '.($i + 1)); } diff --git a/src/Data/MaskPatternTester.php b/src/Data/MaskPatternTester.php new file mode 100644 index 000000000..10a14fcc0 --- /dev/null +++ b/src/Data/MaskPatternTester.php @@ -0,0 +1,215 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +/** + * @link http://www.thonky.com/qr-code-tutorial/data-masking + */ +class MaskPatternTester{ + + /** + * @var \chillerlan\QRCode\Data\QRMatrix + */ + protected $matrix; + + /** + * @var int + */ + protected $moduleCount; + + /** + * @param \chillerlan\QRCode\Data\QRMatrix $matrix + */ + public function setMatrix(QRMatrix $matrix){ + $this->matrix = $matrix; + $this->moduleCount = $this->matrix->size(); + } + + /** + * Returns the penalty for the given mask pattern + * + * @see \chillerlan\QRCode\QRCode::getBestMaskPattern() + * + * @return float + */ + public function testPattern():float{ + $penalty = 0; + + for($level = 1; $level <= 4; $level++){ + $penalty += call_user_func([$this, 'testLevel'.$level]); + } + + return $penalty; + } + + /** + * Checks for each group of five or more same-colored modules in a row (or column) + * + * @return float + */ + protected function testLevel1():float{ + $penalty = 0; + + foreach($this->matrix->matrix() as $y => $row){ + foreach($row as $x => $val){ + $count = 0; + + for($ry = -1; $ry <= 1; $ry++){ + + if($y + $ry < 0 || $this->moduleCount <= $y + $ry){ + continue; + } + + for($rx = -1; $rx <= 1; $rx++){ + + if(($ry === 0 && $rx === 0) || ($x + $rx < 0 || $this->moduleCount <= $x + $rx)){ + continue; + } + + if($this->matrix->check($x + $rx, $y + $ry) === ($val >> 8 > 0)){ + $count++; + } + + } + } + + if($count > 5){ + $penalty += (3 + $count - 5); + } + + } + } + + return $penalty; + } + + /** + * Checks for each 2x2 area of same-colored modules in the matrix + * + * @return float + */ + protected function testLevel2():float{ + $penalty = 0; + + foreach($this->matrix->matrix() as $y => $row){ + + if($y > $this->moduleCount - 2){ + break; + } + + foreach($row as $x => $val){ + + if($x > $this->moduleCount - 2){ + break; + } + + $count = 0; + + if($val >> 8 > 0){ + $count++; + } + + if($this->matrix->check($y, $x + 1)){ + $count++; + } + + if($this->matrix->check($y + 1, $x)){ + $count++; + } + + if($this->matrix->check($y + 1, $x + 1)){ + $count++; + } + + if($count === 0 || $count === 4){ + $penalty += 3; + } + + } + } + + return $penalty; + } + + /** + * Checks if there are patterns that look similar to the finder patterns + * + * @return float + */ + protected function testLevel3():float{ + $penalty = 0; + + foreach($this->matrix->matrix() as $y => $row){ + foreach($row as $x => $val){ + + if($x > $this->moduleCount - 7){ + break; + } + + if( + $val >> 8 > 0 + && !$this->matrix->check($x + 1, $y) + && $this->matrix->check($x + 2, $y) + && $this->matrix->check($x + 3, $y) + && $this->matrix->check($x + 4, $y) + && !$this->matrix->check($x + 5, $y) + && $this->matrix->check($x + 6, $y) + ){ + $penalty += 40; + } + + } + } + + for ($x = 0; $x < $this->moduleCount; $x++) { + for ($y = 0; $y < $this->moduleCount - 6; $y++) { + + if( + $this->matrix->check($x, $y ) + && !$this->matrix->check($x, $y + 1) + && $this->matrix->check($x, $y + 2) + && $this->matrix->check($x, $y + 3) + && $this->matrix->check($x, $y + 4) + && !$this->matrix->check($x, $y + 5) + && $this->matrix->check($x, $y + 6) + ){ + $penalty += 40; + } + + } + } + + return $penalty; + } + + /** + * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference + * + * @return float + */ + protected function testLevel4():float { + $count = 0; + + foreach($this->matrix->matrix() as $y => $row){ + foreach($row as $x => $val){ + if($val >> 8 > 0){ + $count++; + } + } + } + + return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10; + } + + +} diff --git a/src/Data/Number.php b/src/Data/Number.php index d8b90a16b..1a946154a 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -12,17 +12,17 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCode; /** - * + * Numeric mode: decimal digits 0 through 9 */ class Number extends QRDataAbstract{ /** * @var int */ - public $mode = self::MODE_NUMBER; + protected $datamode = QRCode::DATA_NUMBER; /** * @var array @@ -30,25 +30,23 @@ class Number extends QRDataAbstract{ protected $lengthBits = [10, 12, 14]; /** - * @param \chillerlan\QRCode\BitBuffer $buffer - * - * @return void + * @inheritdoc */ - public function write(BitBuffer &$buffer){ + protected function write(string $data){ $i = 0; - while($i + 2 < $this->dataLength){ - $buffer->put($this->parseInt(substr($this->data, $i, 3)), 10); + while($i + 2 < $this->strlen){ + $this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10); $i += 3; } - if($i < $this->dataLength){ + if($i < $this->strlen){ - if($this->dataLength - $i === 1){ - $buffer->put($this->parseInt(substr($this->data, $i, $i + 1)), 4); + if($this->strlen - $i === 1){ + $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4); } - elseif($this->dataLength - $i === 2){ - $buffer->put($this->parseInt(substr($this->data, $i, $i + 2)), 7); + elseif($this->strlen - $i === 2){ + $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7); } } @@ -61,7 +59,7 @@ class Number extends QRDataAbstract{ * @return int * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - private static function parseInt(string $string):int { + protected function parseInt(string $string):int { $num = 0; $len = strlen($string); diff --git a/src/Data/QRDataAbstract.php b/src/Data/QRDataAbstract.php index 42a9f1096..49c5a06ff 100644 --- a/src/Data/QRDataAbstract.php +++ b/src/Data/QRDataAbstract.php @@ -12,20 +12,29 @@ namespace chillerlan\QRCode\Data; +use chillerlan\QRCode\{ + QRCode, QRCodeException, QROptions +}; +use chillerlan\QRCode\Helpers\{ + BitBuffer, Polynomial +}; +use chillerlan\Traits\ClassLoader; + /** * */ abstract class QRDataAbstract implements QRDataInterface{ - - /** - * @var string - */ - public $data; + use ClassLoader; /** * @var int */ - public $dataLength; + protected $strlen; + + /** + * @var int + */ + protected $datamode; /** * @var array @@ -33,37 +42,298 @@ abstract class QRDataAbstract implements QRDataInterface{ protected $lengthBits = [0, 0, 0]; /** - * QRDataAbstract constructor. - * - * @param string $data + * @var int */ - public function __construct(string $data){ - $this->data = $data; - $this->dataLength = strlen($data); + protected $version; + + /** + * @var array + */ + protected $ecdata; + + /** + * @var array + */ + protected $dcdata; + + /** + * @var array + */ + protected $matrixdata; + + /** + * @var \chillerlan\QRCode\QROptions + */ + protected $options; + + /** + * @var \chillerlan\QRCode\Helpers\BitBuffer + */ + protected $bitBuffer; + + /** + * QRDataInterface constructor. + * + * @param \chillerlan\QRCode\QROptions $options + * @param string|null $data + */ + public function __construct(QROptions $options, string $data = null){ + $this->options = $options; + + if($data !== null){ + $this->setData($data); + } } /** - * @see QRCode::createData() + * @param string $data * - * @param int $type + * @return \chillerlan\QRCode\Data\QRDataInterface + */ + public function setData(string $data):QRDataInterface{ + + if($this->datamode === QRCode::DATA_KANJI){ + $data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data)); + } + + $this->strlen = $this->getLength($data); + $this->version = $this->options->version === QRCode::VERSION_AUTO + ? $this->getMinimumVersion() + : $this->options->version; + + $this->matrixdata = $this + ->writeBitBuffer($data) + ->maskECC() + ; + + return $this; + } + + /** + * @param int $maskPattern + * @param bool $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{ + /** @var \chillerlan\QRCode\Data\QRMatrix $matrix */ + $matrix = $this->loadClass(QRMatrix::class, null, $this->version, $this->options->eccLevel); + + return $matrix + ->setFinderPattern() + ->setSeparators() + ->setAlignmentPattern() + ->setTimingPattern() + ->setVersionNumber($test) + ->setFormatInfo($maskPattern, $test) + ->setDarkModule() + ->mapData($this->matrixdata, $maskPattern) + ; + } + + /** + * @param int $version * * @return int * @throws \chillerlan\QRCode\Data\QRCodeDataException * @codeCoverageIgnore */ - public function getLengthInBits(int $type):int { + protected function getLengthBits(int $version):int { - switch(true){ - case $type >= 1 && $type <= 9: - return $this->lengthBits[0]; // 1 - 9 - case $type <= 26: - return $this->lengthBits[1]; // 10 - 26 - case $type <= 40: - return $this->lengthBits[2]; // 27 - 40 - default: - throw new QRCodeDataException('$type: '.$type); + foreach([9, 26, 40] as $key => $breakpoint){ + if($version <= $breakpoint){ + return $this->lengthBits[$key]; + } + } + + throw new QRCodeDataException('invalid version number: '.$version); + } + + /** + * returns the byte count of the string + * + * @param string $data + * + * @return int + */ + protected function getLength(string $data):int{ + return strlen($data); + } + + /** + * @return int + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + protected function getMinimumVersion():int{ + + // guess the version number within the given range + foreach(range(max(1, $this->options->versionMin), min($this->options->versionMax, 40)) as $version){ + $maxlength = self::MAX_LENGTH[$version][QRCode::DATA_MODES[$this->datamode]][QRCode::ECC_MODES[$this->options->eccLevel]]; + + if($this->strlen <= $maxlength){ + return $version; + } } + throw new QRCodeDataException('data exceeds '.$maxlength.' characters'); + } + + /** + * writes the $data bits to $this->bitBuffer + * + * @param string $data + * + * @return void + */ + abstract protected function write(string $data); + + /** + * @param string $data + * + * @return \chillerlan\QRCode\Data\QRDataAbstract + * @throws \chillerlan\QRCode\QRCodeException + */ + protected function writeBitBuffer(string $data):QRDataInterface { + $this->bitBuffer = new BitBuffer; + + // @todo: fixme, get real length + $MAX_BITS = self::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]]; + + $this->bitBuffer + ->clear() + ->put($this->datamode, 4) + ->put($this->strlen, $this->getLengthBits($this->version)) + ; + + $this->write($data); + + if($this->bitBuffer->length > $MAX_BITS){ + throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)'); + } + + // end code. + if($this->bitBuffer->length + 4 <= $MAX_BITS){ + $this->bitBuffer->put(0, 4); + } + + // padding + while($this->bitBuffer->length % 8 !== 0){ + $this->bitBuffer->putBit(false); + } + + // padding + while(true){ + + if($this->bitBuffer->length >= $MAX_BITS){ + break; + } + + $this->bitBuffer->put(0xEC, 8); + + if($this->bitBuffer->length >= $MAX_BITS){ + break; + } + + $this->bitBuffer->put(0x11, 8); + } + + return $this; + } + + /** + * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding + * + * @return array + */ + protected function maskECC():array { + list($l1, $l2, $b1, $b2) = self::RSBLOCKS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]]; + + $rsBlocks = array_fill(0, $l1, [$b1, $b2]); + $rsCount = $l1 + $l2; + $this->ecdata = array_fill(0, $rsCount, null); + $this->dcdata = $this->ecdata; + + if($l2 > 0){ + $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1])); + } + + $totalCodeCount = 0; + $maxDcCount = 0; + $maxEcCount = 0; + $offset = 0; + + foreach($rsBlocks as $key => $block){ + list($rsBlockTotal, $dcCount) = $block; + + $ecCount = $rsBlockTotal - $dcCount; + $maxDcCount = max($maxDcCount, $dcCount); + $maxEcCount = max($maxEcCount, $ecCount); + $this->dcdata[$key] = array_fill(0, $dcCount, null); + + foreach($this->dcdata[$key] as $a => $_z){ + $this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset]; + } + + list($num, $add) = $this->poly($key, $ecCount); + + foreach($this->ecdata[$key] as $c => $_z){ + $modIndex = $c + $add; + $this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0; + } + + $offset += $dcCount; + $totalCodeCount += $rsBlockTotal; + } + + $data = array_fill(0, $totalCodeCount, null); + $index = 0; + + $mask = function($arr, $count) use (&$data, &$index, $rsCount){ + for($x = 0; $x < $count; $x++){ + for($y = 0; $y < $rsCount; $y++){ + if($x < count($arr[$y])){ + $data[$index] = $arr[$y][$x]; + $index++; + } + } + } + }; + + $mask($this->dcdata, $maxDcCount); + $mask($this->ecdata, $maxEcCount); + + return $data; + } + + /** + * @param int $key + * @param int $count + * + * @return int[] + */ + protected function poly(int $key, int $count):array{ + $rsPoly = new Polynomial; + $modPoly = new Polynomial; + + for($i = 0; $i < $count; $i++){ + $modPoly->setNum([1, $modPoly->gexp($i)]); + $rsPoly->multiply($modPoly->getNum()); + } + + $rsPolyCount = count($rsPoly->getNum()); + + $modPoly + ->setNum($this->dcdata[$key], $rsPolyCount - 1) + ->mod($rsPoly->getNum()) + ; + + $this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null); + $num = $modPoly->getNum(); + + return [ + $num, + count($num) - count($this->ecdata[$key]), + ]; } } diff --git a/src/Data/QRDataInterface.php b/src/Data/QRDataInterface.php index 9063e8453..efc034c35 100644 --- a/src/Data/QRDataInterface.php +++ b/src/Data/QRDataInterface.php @@ -12,39 +12,161 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\BitBuffer; - /** - * @property string data - * @property int dataLength - * @property int mode + * */ interface QRDataInterface{ - const MODE_NUMBER = 1 << 0; - const MODE_ALPHANUM = 1 << 1; - const MODE_BYTE = 1 << 2; - const MODE_KANJI = 1 << 3; + /** + * @link http://www.qrcode.com/en/about/version.html + */ + const MAX_LENGTH =[ 1 => // start at 1 + // [NUMERIC => [L, M, Q, H ], ALPHANUM => [L, M, Q, H], BINARY => [L, M, Q, H ], KANJI => [L, M, Q, H ]] // modules + [[ 41, 34, 27, 17], [ 25, 20, 16, 10], [ 17, 14, 11, 7], [ 10, 8, 7, 4]], // 21 + [[ 77, 63, 48, 34], [ 47, 38, 29, 20], [ 32, 26, 20, 14], [ 20, 16, 12, 8]], // 25 + [[ 127, 101, 77, 58], [ 77, 61, 47, 35], [ 53, 42, 32, 24], [ 32, 26, 20, 15]], // 29 + [[ 187, 149, 111, 82], [ 114, 90, 67, 50], [ 78, 62, 46, 34], [ 48, 38, 28, 21]], // 33 + [[ 255, 202, 144, 106], [ 154, 122, 87, 64], [ 106, 84, 60, 44], [ 65, 52, 37, 27]], // 37 + [[ 322, 255, 178, 139], [ 195, 154, 108, 84], [ 134, 106, 74, 58], [ 82, 65, 45, 36]], // 41 + [[ 370, 293, 207, 154], [ 224, 178, 125, 93], [ 154, 122, 86, 64], [ 95, 75, 53, 39]], // 45 + [[ 461, 365, 259, 202], [ 279, 221, 157, 122], [ 192, 152, 108, 84], [ 118, 93, 66, 52]], // 49 + [[ 552, 432, 312, 235], [ 335, 262, 189, 143], [ 230, 180, 130, 98], [ 141, 111, 80, 60]], // 53 + [[ 652, 513, 364, 288], [ 395, 311, 221, 174], [ 271, 213, 151, 119], [ 167, 131, 93, 74]], // 57 + [[ 772, 604, 427, 331], [ 468, 366, 259, 200], [ 321, 251, 177, 137], [ 198, 155, 109, 85]], // 61 + [[ 883, 691, 489, 374], [ 535, 419, 296, 227], [ 367, 287, 203, 155], [ 226, 177, 125, 96]], // 65 + [[1022, 796, 580, 427], [ 619, 483, 352, 259], [ 425, 331, 241, 177], [ 262, 204, 149, 109]], // 69 NICE! + [[1101, 871, 621, 468], [ 667, 528, 376, 283], [ 458, 362, 258, 194], [ 282, 223, 159, 120]], // 73 + [[1250, 991, 703, 530], [ 758, 600, 426, 321], [ 520, 412, 292, 220], [ 320, 254, 180, 136]], // 77 + [[1408, 1082, 775, 602], [ 854, 656, 470, 365], [ 586, 450, 322, 250], [ 361, 277, 198, 154]], // 81 + [[1548, 1212, 876, 674], [ 938, 734, 531, 408], [ 644, 504, 364, 280], [ 397, 310, 224, 173]], // 85 + [[1725, 1346, 948, 746], [1046, 816, 574, 452], [ 718, 560, 394, 310], [ 442, 345, 243, 191]], // 89 + [[1903, 1500, 1063, 813], [1153, 909, 644, 493], [ 792, 624, 442, 338], [ 488, 384, 272, 208]], // 93 + [[2061, 1600, 1159, 919], [1249, 970, 702, 557], [ 858, 666, 482, 382], [ 528, 410, 297, 235]], // 97 + [[2232, 1708, 1224, 969], [1352, 1035, 742, 587], [ 929, 711, 509, 403], [ 572, 438, 314, 248]], // 101 + [[2409, 1872, 1358, 1056], [1460, 1134, 823, 640], [1003, 779, 565, 439], [ 618, 480, 348, 270]], // 105 + [[2620, 2059, 1468, 1108], [1588, 1248, 890, 672], [1091, 857, 611, 461], [ 672, 528, 376, 284]], // 109 + [[2812, 2188, 1588, 1228], [1704, 1326, 963, 744], [1171, 911, 661, 511], [ 721, 561, 407, 315]], // 113 + [[3057, 2395, 1718, 1286], [1853, 1451, 1041, 779], [1273, 997, 715, 535], [ 784, 614, 440, 330]], // 117 + [[3283, 2544, 1804, 1425], [1990, 1542, 1094, 864], [1367, 1059, 751, 593], [ 842, 652, 462, 365]], // 121 + [[3517, 2701, 1933, 1501], [2132, 1637, 1172, 910], [1465, 1125, 805, 625], [ 902, 692, 496, 385]], // 125 + [[3669, 2857, 2085, 1581], [2223, 1732, 1263, 958], [1528, 1190, 868, 658], [ 940, 732, 534, 405]], // 129 + [[3909, 3035, 2181, 1677], [2369, 1839, 1322, 1016], [1628, 1264, 908, 698], [1002, 778, 559, 430]], // 133 + [[4158, 3289, 2358, 1782], [2520, 1994, 1429, 1080], [1732, 1370, 982, 742], [1066, 843, 604, 457]], // 137 + [[4417, 3486, 2473, 1897], [2677, 2113, 1499, 1150], [1840, 1452, 1030, 790], [1132, 894, 634, 486]], // 141 + [[4686, 3693, 2670, 2022], [2840, 2238, 1618, 1226], [1952, 1538, 1112, 842], [1201, 947, 684, 518]], // 145 + [[4965, 3909, 2805, 2157], [3009, 2369, 1700, 1307], [2068, 1628, 1168, 898], [1273, 1002, 719, 553]], // 149 + [[5253, 4134, 2949, 2301], [3183, 2506, 1787, 1394], [2188, 1722, 1228, 958], [1347, 1060, 756, 590]], // 153 + [[5529, 4343, 3081, 2361], [3351, 2632, 1867, 1431], [2303, 1809, 1283, 983], [1417, 1113, 790, 605]], // 157 + [[5836, 4588, 3244, 2524], [3537, 2780, 1966, 1530], [2431, 1911, 1351, 1051], [1496, 1176, 832, 647]], // 161 + [[6153, 4775, 3417, 2625], [3729, 2894, 2071, 1591], [2563, 1989, 1423, 1093], [1577, 1224, 876, 673]], // 165 + [[6479, 5039, 3599, 2735], [3927, 3054, 2181, 1658], [2699, 2099, 1499, 1139], [1661, 1292, 923, 701]], // 169 + [[6743, 5313, 3791, 2927], [4087, 3220, 2298, 1774], [2809, 2213, 1579, 1219], [1729, 1362, 972, 750]], // 173 + [[7089, 5596, 3993, 3057], [4296, 3391, 2420, 1852], [2953, 2331, 1663, 1273], [1817, 1435, 1024, 784]], // 177 + ]; - const MODE = [ - self::MODE_NUMBER => 0, - self::MODE_ALPHANUM => 1, - self::MODE_BYTE => 2, - self::MODE_KANJI => 3, + const MAX_BITS = [ 1 => // start at 1 + // MAX_BITS => [L, M, Q, H ] + [ 152, 128, 104, 72], + [ 272, 224, 176, 128], + [ 440, 352, 272, 208], + [ 640, 512, 384, 288], + [ 864, 688, 496, 368], + [ 1088, 864, 608, 480], + [ 1248, 992, 704, 528], + [ 1552, 1232, 880, 688], + [ 1856, 1456, 1056, 800], + [ 2192, 1728, 1232, 976], + [ 2592, 2032, 1440, 1120], + [ 2960, 2320, 1648, 1264], + [ 3424, 2672, 1952, 1440], + [ 3688, 2920, 2088, 1576], + [ 4184, 3320, 2360, 1784], + [ 4712, 3624, 2600, 2024], + [ 5176, 4056, 2936, 2264], + [ 5768, 4504, 3176, 2504], + [ 6360, 5016, 3560, 2728], + [ 6888, 5352, 3880, 3080], + [ 7456, 5712, 4096, 3248], + [ 8048, 6256, 4544, 3536], + [ 8752, 6880, 4912, 3712], + [ 9392, 7312, 5312, 4112], + [10208, 8000, 5744, 4304], + [10960, 8496, 6032, 4768], + [11744, 9024, 6464, 5024], + [12248, 9544, 6968, 5288], + [13048, 10136, 7288, 5608], + [13880, 10984, 7880, 5960], + [14744, 11640, 8264, 6344], + [15640, 12328, 8920, 6760], + [16568, 13048, 7208, 9368], + [17528, 13800, 9848, 7688], + [18448, 14496, 10288, 7888], + [19472, 15312, 10832, 8432], + [20528, 15936, 11408, 8768], + [21616, 16816, 12016, 9136], + [22496, 17728, 12656, 9776], + [23648, 18672, 13328, 10208], ]; /** - * @param \chillerlan\QRCode\BitBuffer $buffer - * @return void + * @link http://www.thonky.com/qr-code-tutorial/error-correction-table */ - public function write(BitBuffer &$buffer); + const RSBLOCKS = [ 1 => // start at 1 + [[ 1, 0, 26, 19], [ 1, 0, 26, 16], [ 1, 0, 26, 13], [ 1, 0, 26, 9]], // 1 + [[ 1, 0, 44, 34], [ 1, 0, 44, 28], [ 1, 0, 44, 22], [ 1, 0, 44, 16]], // + [[ 1, 0, 70, 55], [ 1, 0, 70, 44], [ 2, 0, 35, 17], [ 2, 0, 35, 13]], // + [[ 1, 0, 100, 80], [ 2, 0, 50, 32], [ 2, 0, 50, 24], [ 4, 0, 25, 9]], // + [[ 1, 0, 134, 108], [ 2, 0, 67, 43], [ 2, 2, 33, 15], [ 2, 2, 33, 11]], // 5 + [[ 2, 0, 86, 68], [ 4, 0, 43, 27], [ 4, 0, 43, 19], [ 4, 0, 43, 15]], // + [[ 2, 0, 98, 78], [ 4, 0, 49, 31], [ 2, 4, 32, 14], [ 4, 1, 39, 13]], // + [[ 2, 0, 121, 97], [ 2, 2, 60, 38], [ 4, 2, 40, 18], [ 4, 2, 40, 14]], // + [[ 2, 0, 146, 116], [ 3, 2, 58, 36], [ 4, 4, 36, 16], [ 4, 4, 36, 12]], // + [[ 2, 2, 86, 68], [ 4, 1, 69, 43], [ 6, 2, 43, 19], [ 6, 2, 43, 15]], // 10 + [[ 4, 0, 101, 81], [ 1, 4, 80, 50], [ 4, 4, 50, 22], [ 3, 8, 36, 12]], // + [[ 2, 2, 116, 92], [ 6, 2, 58, 36], [ 4, 6, 46, 20], [ 7, 4, 42, 14]], // + [[ 4, 0, 133, 107], [ 8, 1, 59, 37], [ 8, 4, 44, 20], [12, 4, 33, 11]], // + [[ 3, 1, 145, 115], [ 4, 5, 64, 40], [11, 5, 36, 16], [11, 5, 36, 12]], // + [[ 5, 1, 109, 87], [ 5, 5, 65, 41], [ 5, 7, 54, 24], [11, 7, 36, 12]], // 15 + [[ 5, 1, 122, 98], [ 7, 3, 73, 45], [15, 2, 43, 19], [ 3, 13, 45, 15]], // + [[ 1, 5, 135, 107], [10, 1, 74, 46], [ 1, 15, 50, 22], [ 2, 17, 42, 14]], // + [[ 5, 1, 150, 120], [ 9, 4, 69, 43], [17, 1, 50, 22], [ 2, 19, 42, 14]], // + [[ 3, 4, 141, 113], [ 3, 11, 70, 44], [17, 4, 47, 21], [ 9, 16, 39, 13]], // + [[ 3, 5, 135, 107], [ 3, 13, 67, 41], [15, 5, 54, 24], [15, 10, 43, 15]], // 20 + [[ 4, 4, 144, 116], [17, 0, 68, 42], [17, 6, 50, 22], [19, 6, 46, 16]], // + [[ 2, 7, 139, 111], [17, 0, 74, 46], [ 7, 16, 54, 24], [34, 0, 37, 13]], // + [[ 4, 5, 151, 121], [ 4, 14, 75, 47], [11, 14, 54, 24], [16, 14, 45, 15]], // + [[ 6, 4, 147, 117], [ 6, 14, 73, 45], [11, 16, 54, 24], [30, 2, 46, 16]], // + [[ 8, 4, 132, 106], [ 8, 13, 75, 47], [ 7, 22, 54, 24], [22, 13, 45, 15]], // 25 + [[10, 2, 142, 114], [19, 4, 74, 46], [28, 6, 50, 22], [33, 4, 46, 16]], // + [[ 8, 4, 152, 122], [22, 3, 73, 45], [ 8, 26, 53, 23], [12, 28, 45, 15]], // + [[ 3, 10, 147, 117], [ 3, 23, 73, 45], [ 4, 31, 54, 24], [11, 31, 45, 15]], // + [[ 7, 7, 146, 116], [21, 7, 73, 45], [ 1, 37, 53, 23], [19, 26, 45, 15]], // + [[ 5, 10, 145, 115], [19, 10, 75, 47], [15, 25, 54, 24], [23, 25, 45, 15]], // 30 + [[13, 3, 145, 115], [ 2, 29, 74, 46], [42, 1, 54, 24], [23, 28, 45, 15]], // + [[17, 0, 145, 115], [10, 23, 74, 46], [10, 35, 54, 24], [19, 35, 45, 15]], // + [[17, 1, 145, 115], [14, 21, 74, 46], [29, 19, 54, 24], [11, 46, 45, 15]], // + [[13, 6, 145, 115], [14, 23, 74, 46], [44, 7, 54, 24], [59, 1, 46, 16]], // + [[12, 7, 151, 121], [12, 26, 75, 47], [39, 14, 54, 24], [22, 41, 45, 15]], // 35 + [[ 6, 14, 151, 121], [ 6, 34, 75, 47], [46, 10, 54, 24], [ 2, 64, 45, 15]], // + [[17, 4, 152, 122], [29, 14, 74, 46], [49, 10, 54, 24], [24, 46, 45, 15]], // + [[ 4, 18, 152, 122], [13, 32, 74, 46], [48, 14, 54, 24], [42, 32, 45, 15]], // + [[20, 4, 147, 117], [40, 7, 75, 47], [43, 22, 54, 24], [10, 67, 45, 15]], // + [[19, 6, 148, 118], [18, 31, 75, 47], [34, 34, 54, 24], [20, 61, 45, 15]], // 40 + ]; /** - * @param $type + * @param string $data * - * @return int - * @throws \chillerlan\QRCode\Data\QRCodeDataException + * @return \chillerlan\QRCode\Data\QRDataInterface */ - public function getLengthInBits(int $type):int; + public function setData(string $data); + + /** + * @param int $maskPattern + * @param bool $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function initMatrix(int $maskPattern, bool $test = null):QRMatrix; } diff --git a/src/Data/QRMatrix.php b/src/Data/QRMatrix.php new file mode 100644 index 000000000..104a8afdf --- /dev/null +++ b/src/Data/QRMatrix.php @@ -0,0 +1,563 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCode; + +/** + * @link http://www.thonky.com/qr-code-tutorial/format-version-information + */ +class QRMatrix{ + + const M_NULL = 0x00; + const M_DARKMODULE = 0x02; + const M_DATA = 0x04; + const M_FINDER = 0x06; + const M_SEPARATOR = 0x08; + const M_ALIGNMENT = 0x0a; + const M_TIMING = 0x0c; + const M_FORMAT = 0x0e; + const M_VERSION = 0x10; + const M_QUIETZONE = 0x12; + const M_LOGO = 0x14; // @todo + + const M_TEST = 0xff; + + /** + * @link http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations + */ + const alignmentPattern = [ 1 => // start at 1 + [], + [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], + ]; + + /** + * @link http://www.thonky.com/qr-code-tutorial/format-version-tables + */ + const versionPattern = [ 7 => // no version pattern for QR Codes < 7 + 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, // 7-10 + 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, + 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, + 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69, + ]; + + const formatPattern = [ + [0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976], // L + [0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0], // M + [0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed], // Q + [0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b], // H + ]; + + /** + * @var int + */ + protected $version; + + /** + * @var int + */ + protected $eclevel; + + /** + * @var int + */ + protected $maskPattern = -1; + + /** + * @var int + */ + protected $moduleCount; + + /** + * @var mixed[] + */ + protected $matrix; + + /** + * QRMatrix constructor. + * + * @param int $version + * @param int $eclevel + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function __construct(int $version, int $eclevel){ + + if(!in_array($version, range(1, 40), true)){ + throw new QRCodeDataException('invalid QR Code version'); + } + + if(!array_key_exists($eclevel, QRCode::ECC_MODES)){ + throw new QRCodeDataException('invalid ecc level'); + } + + $this->version = $version; + $this->eclevel = $eclevel; + $this->moduleCount = $this->version * 4 + 17; + $this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, self::M_NULL)); + } + + /** + * Returns the data array + * + * @return array + */ + public function matrix():array { + return $this->matrix; + } + + /** + * @return int + */ + public function version():int { + return $this->version; + } + + /** + * @return int + */ + public function eccLevel():int { + return $this->eclevel; + } + + /** + * @return int + */ + public function maskPattern():int { + return $this->maskPattern; + } + + /** + * Returns the absoulute size of the matrix, including quiet zone (after setting it). + * + * size = version * 4 + 17 [ + 2 * quietzone size] + * + * @return int + */ + public function size():int{ + return $this->moduleCount; + } + + /** + * Returns the value of the module at position [$x, $y] + * + * @param int $x + * @param int $y + * + * @return int + */ + public function get(int $x, int $y):int{ + return $this->matrix[$y][$x]; + } + + /** + * Sets the $M_TYPE value for the module at position [$x, $y] + * + * true => $M_TYPE << 8 + * false => $M_TYPE + * + * @param int $x + * @param int $y + * @param int $M_TYPE + * @param bool $value + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{ + $this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0); + + return $this; + } + + /** + * Checks whether a module is true (dark) or false (light) + * + * true => $value >> 8 === $M_TYPE + * $value >> 8 > 0 + * + * false => $value === $M_TYPE + * $value >> 8 === 0 + * + * @param int $x + * @param int $y + * + * @return bool + */ + public function check(int $x, int $y):bool{ + return $this->matrix[$y][$x] >> 8 > 0; + } + + + /** + * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setDarkModule():QRMatrix{ + $this->set(8, 4 * $this->version + 9, true, self::M_DARKMODULE); + + return $this; + } + + /** + * Draws the 7x7 finder patterns in the corners top left/right and bottom left + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setFinderPattern():QRMatrix{ + + $pos = [ + [0, 0], // top left + [$this->moduleCount - 7, 0], // bottom left + [0, $this->moduleCount - 7], // top right + ]; + + foreach($pos as $c){ + for($y = 0; $y < 7; $y++){ + for($x = 0; $x < 7; $x++){ + $this->set( + $c[0] + $y, + $c[1] + $x, + !(($x > 0 && $x < 6 && ($y === 1 || $y === 5)) || ($y > 0 && $y < 6 && ($x === 1 || $x === 5))), + self::M_FINDER + ); + } + } + } + + return $this; + } + + /** + * Draws the separator lines around the finder patterns + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setSeparators():QRMatrix{ + + $h = [ + [7, 0], + [$this->moduleCount - 8, 0], + [7, $this->moduleCount - 8], + ]; + + $v = [ + [7, 7], + [$this->moduleCount - 1, 7], + [7, $this->moduleCount - 8], + ]; + + $t = self::M_SEPARATOR; + + for($c = 0; $c < 3; $c++){ + for($i = 0; $i < 8; $i++){ + $this->set($h[$c][0] , $h[$c][1] + $i, false, $t); + $this->set($v[$c][0] - $i, $v[$c][1] , false, $t); + } + } + + return $this; + } + + + /** + * Draws the 5x5 alignment patterns + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setAlignmentPattern():QRMatrix{ + $pattern = self::alignmentPattern[$this->version]; + + foreach($pattern as $y){ + foreach($pattern as $x){ + + // skip existing patterns + if($this->matrix[$y][$x] !== self::M_NULL){ + continue; + } + + for($ry = -2; $ry <= 2; $ry++){ + for($rx = -2; $rx <= 2; $rx++){ + $v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2; + + $this->set($x + $rx, $y + $ry, $v, self::M_ALIGNMENT); + } + } + + } + } + + return $this; + } + + + /** + * Draws the timing pattern (h/v checkered line between the finder patterns) + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setTimingPattern():QRMatrix{ + + foreach(range(8, $this->moduleCount - 8 - 1) as $i){ + + if($this->matrix[6][$i] !== self::M_NULL || $this->matrix[$i][6] !== self::M_NULL){ + continue; + } + + $v = $i % 2 === 0; + $t = self::M_TIMING; + + $this->set($i, 6, $v, $t); // h + $this->set(6, $i, $v, $t); // v + } + + return $this; + } + + /** + * Draws the version information, 2x 3x6 pixel + * + * @param bool|null $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setVersionNumber(bool $test = null):QRMatrix{ + $test = $test !== null ? $test : false; + + $bits = self::versionPattern[$this->version] ?? false; + + if($bits !== false){ + + for($i = 0; $i < 18; $i++){ + $a = (int)floor($i / 3); + $b = $i % 3 + $this->moduleCount - 8 - 3; + $v = !$test && (($bits >> $i) & 1) === 1; + $t = self::M_VERSION; + + $this->set($b, $a, $v, $t); // ne + $this->set($a, $b, $v, $t); // sw + } + + } + + return $this; + } + + /** + * Draws the format info along the finder patterns + * + * @param int $maskPattern + * @param bool|null $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{ + $test = $test !== null ? $test : false; + $bits = self::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0; + $t = self::M_FORMAT; + + for($i = 0; $i < 15; $i++){ + $v = !$test && (($bits >> $i) & 1) === 1; + + if($i < 6){ + $this->set(8, $i, $v, $t); + } + elseif($i < 8){ + $this->set(8, $i + 1, $v, $t); + } + else{ + $this->set(8, $this->moduleCount - 15 + $i, $v, $t); + } + + if($i < 8){ + $this->set($this->moduleCount - $i - 1, 8, $v, $t); + } + elseif($i < 9){ + $this->set(15 - $i, 8, $v, $t); + } + else{ + $this->set(15 - $i - 1, 8, $v, $t); + } + + } + + $this->set(8, $this->moduleCount - 8, !$test, $t); + + return $this; + } + + /** + * Draws the "quiet zone" of $size around the matrix + * + * @param int|null $size + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setQuietZone(int $size = null):QRMatrix{ + + if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === self::M_NULL){ + throw new QRCodeDataException('use only after writing data'); + } + + $size = $size !== null ? max(0, min($size, floor($this->moduleCount / 2))) : 4; + $t = self::M_QUIETZONE; + + for($y = 0; $y < $this->moduleCount; $y++){ + for($i = 0; $i < $size; $i++){ + array_unshift($this->matrix[$y], $t); + array_push($this->matrix[$y], $t); + } + } + + $this->moduleCount += ($size * 2); + $r = array_fill(0, $this->moduleCount, $t); + + for($i = 0; $i < $size; $i++){ + array_unshift($this->matrix, $r); + array_push($this->matrix, $r); + } + + return $this; + } + + /** + * Maps the binary $data array from QRDataInterface::maskECC() on the matrix, using $maskPattern + * + * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC() + * + * @param int[] $data + * @param int $maskPattern + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function mapData(array $data, int $maskPattern):QRMatrix{ + $this->maskPattern = $maskPattern; + $byteCount = count($data); + $size = $this->moduleCount - 1; + + for($i = $size, $y = $size, $inc = -1, $byteIndex = 0, $bitIndex = 7; $i > 0; $i -= 2){ + + if($i === 6){ + $i--; + } + + while(true){ + for($c = 0; $c < 2; $c++){ + $x = $i - $c; + + if($this->matrix[$y][$x] === self::M_NULL){ + $v = false; + + if($byteIndex < $byteCount){ + $v = (($data[$byteIndex] >> $bitIndex) & 1) === 1; + } + + if($this->getMask($x, $y, $maskPattern) === 0){ + $v = !$v; + } + + $this->matrix[$y][$x] = self::M_DATA << ($v ? 8 : 0); + $bitIndex--; + + if($bitIndex === -1){ + $byteIndex++; + $bitIndex = 7; + } + + } + } + + $y += $inc; + + if($y < 0 || $this->moduleCount <= $y){ + $y -= $inc; + $inc = -$inc; + + break; + } + + } + } + + return $this; + } + + /** + * @see \chillerlan\QRCode\QRMatrix::mapData() + * + * @internal + * + * @param int $x + * @param int $y + * @param int $maskPattern + * + * @return int + */ + protected function getMask(int $x, int $y, int $maskPattern):int { + $a = $y + $x; + $m = $y * $x; + + // i hate it so much + switch($maskPattern){ + case 0: return $a % 2; + case 1: return $y % 2; + case 2: return $x % 3; + case 3: return $a % 3; + case 4: return (floor($y / 2) + floor($x / 3)) % 2; + case 5: return $m % 2 + $m % 3; + case 6: return ($m % 2 + $m % 3) % 2; + case 7: return ($m % 3 + $a % 2) % 2; + } + + throw new QRCodeDataException('invalid mask pattern'); + } + +} diff --git a/src/BitBuffer.php b/src/Helpers/BitBuffer.php similarity index 74% rename from src/BitBuffer.php rename to src/Helpers/BitBuffer.php index 813ad46d7..62516a835 100644 --- a/src/BitBuffer.php +++ b/src/Helpers/BitBuffer.php @@ -4,21 +4,22 @@ * * @filesource BitBuffer.php * @created 25.11.2015 - * @package chillerlan\QRCode + * @package chillerlan\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace chillerlan\QRCode; +namespace chillerlan\QRCode\Helpers; /** - * + * @property int[] $buffer + * @property int $length */ class BitBuffer{ /** - * @var array + * @var int[] */ public $buffer = []; @@ -28,7 +29,7 @@ class BitBuffer{ public $length = 0; /** - * @return \chillerlan\QRCode\BitBuffer + * @return \chillerlan\QRCode\Helpers\BitBuffer */ public function clear():BitBuffer{ $this->buffer = []; @@ -41,7 +42,7 @@ class BitBuffer{ * @param int $num * @param int $length * - * @return \chillerlan\QRCode\BitBuffer + * @return \chillerlan\QRCode\Helpers\BitBuffer */ public function put(int $num, int $length):BitBuffer{ @@ -55,7 +56,7 @@ class BitBuffer{ /** * @param bool $bit * - * @return \chillerlan\QRCode\BitBuffer + * @return \chillerlan\QRCode\Helpers\BitBuffer */ public function putBit(bool $bit):BitBuffer{ $bufIndex = floor($this->length / 8); @@ -64,7 +65,7 @@ class BitBuffer{ $this->buffer[] = 0; } - if($bit){ + if($bit === true){ $this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8)); } diff --git a/src/Helpers/Polynomial.php b/src/Helpers/Polynomial.php new file mode 100644 index 000000000..ac86f3421 --- /dev/null +++ b/src/Helpers/Polynomial.php @@ -0,0 +1,182 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Helpers; + +use chillerlan\QRCode\QRCodeException; + +/** + * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding + */ +class Polynomial{ + + /** + * @link http://www.thonky.com/qr-code-tutorial/log-antilog-table + */ + const table = [ + [ 1, 0], [ 2, 0], [ 4, 1], [ 8, 25], [ 16, 2], [ 32, 50], [ 64, 26], [128, 198], + [ 29, 3], [ 58, 223], [116, 51], [232, 238], [205, 27], [135, 104], [ 19, 199], [ 38, 75], + [ 76, 4], [152, 100], [ 45, 224], [ 90, 14], [180, 52], [117, 141], [234, 239], [201, 129], + [143, 28], [ 3, 193], [ 6, 105], [ 12, 248], [ 24, 200], [ 48, 8], [ 96, 76], [192, 113], + [157, 5], [ 39, 138], [ 78, 101], [156, 47], [ 37, 225], [ 74, 36], [148, 15], [ 53, 33], + [106, 53], [212, 147], [181, 142], [119, 218], [238, 240], [193, 18], [159, 130], [ 35, 69], + [ 70, 29], [140, 181], [ 5, 194], [ 10, 125], [ 20, 106], [ 40, 39], [ 80, 249], [160, 185], + [ 93, 201], [186, 154], [105, 9], [210, 120], [185, 77], [111, 228], [222, 114], [161, 166], + [ 95, 6], [190, 191], [ 97, 139], [194, 98], [153, 102], [ 47, 221], [ 94, 48], [188, 253], + [101, 226], [202, 152], [137, 37], [ 15, 179], [ 30, 16], [ 60, 145], [120, 34], [240, 136], + [253, 54], [231, 208], [211, 148], [187, 206], [107, 143], [214, 150], [177, 219], [127, 189], + [254, 241], [225, 210], [223, 19], [163, 92], [ 91, 131], [182, 56], [113, 70], [226, 64], + [217, 30], [175, 66], [ 67, 182], [134, 163], [ 17, 195], [ 34, 72], [ 68, 126], [136, 110], + [ 13, 107], [ 26, 58], [ 52, 40], [104, 84], [208, 250], [189, 133], [103, 186], [206, 61], + [129, 202], [ 31, 94], [ 62, 155], [124, 159], [248, 10], [237, 21], [199, 121], [147, 43], + [ 59, 78], [118, 212], [236, 229], [197, 172], [151, 115], [ 51, 243], [102, 167], [204, 87], + [133, 7], [ 23, 112], [ 46, 192], [ 92, 247], [184, 140], [109, 128], [218, 99], [169, 13], + [ 79, 103], [158, 74], [ 33, 222], [ 66, 237], [132, 49], [ 21, 197], [ 42, 254], [ 84, 24], + [168, 227], [ 77, 165], [154, 153], [ 41, 119], [ 82, 38], [164, 184], [ 85, 180], [170, 124], + [ 73, 17], [146, 68], [ 57, 146], [114, 217], [228, 35], [213, 32], [183, 137], [115, 46], + [230, 55], [209, 63], [191, 209], [ 99, 91], [198, 149], [145, 188], [ 63, 207], [126, 205], + [252, 144], [229, 135], [215, 151], [179, 178], [123, 220], [246, 252], [241, 190], [255, 97], + [227, 242], [219, 86], [171, 211], [ 75, 171], [150, 20], [ 49, 42], [ 98, 93], [196, 158], + [149, 132], [ 55, 60], [110, 57], [220, 83], [165, 71], [ 87, 109], [174, 65], [ 65, 162], + [130, 31], [ 25, 45], [ 50, 67], [100, 216], [200, 183], [141, 123], [ 7, 164], [ 14, 118], + [ 28, 196], [ 56, 23], [112, 73], [224, 236], [221, 127], [167, 12], [ 83, 111], [166, 246], + [ 81, 108], [162, 161], [ 89, 59], [178, 82], [121, 41], [242, 157], [249, 85], [239, 170], + [195, 251], [155, 96], [ 43, 134], [ 86, 177], [172, 187], [ 69, 204], [138, 62], [ 9, 90], + [ 18, 203], [ 36, 89], [ 72, 95], [144, 176], [ 61, 156], [122, 169], [244, 160], [245, 81], + [247, 11], [243, 245], [251, 22], [235, 235], [203, 122], [139, 117], [ 11, 44], [ 22, 215], + [ 44, 79], [ 88, 174], [176, 213], [125, 233], [250, 230], [233, 231], [207, 173], [131, 232], + [ 27, 116], [ 54, 214], [108, 244], [216, 234], [173, 168], [ 71, 80], [142, 88], [ 1, 175], + ]; + + /** + * @var array + */ + protected $num = []; + + /** + * Polynomial constructor. + * + * @param array $num + * @param int $shift + */ + public function __construct(array $num = [1], int $shift = 0){ + $this->setNum($num, $shift); + } + + /** + * @return array + */ + public function getNum():array { + return $this->num; + } + + /** + * @param array $num + * @param int $shift + * + * @return \chillerlan\QRCode\Helpers\Polynomial + */ + public function setNum(array $num, int $shift = 0):Polynomial { + $offset = 0; + $numCount = count($num); + + while($offset < $numCount && $num[$offset] === 0){ + $offset++; + } + + $this->num = array_fill(0, $numCount - $offset + $shift, 0); + + for($i = 0; $i < $numCount - $offset; $i++){ + $this->num[$i] = $num[$i + $offset]; + } + + return $this; + } + + /** + * @param array $e + * + * @return \chillerlan\QRCode\Helpers\Polynomial + */ + public function multiply(array $e):Polynomial { + $n = array_fill(0, count($this->num) + count($e) - 1, 0); + + foreach($this->num as $i => $vi){ + $vi = $this->glog($vi); + + foreach($e as $j => $vj){ + $n[$i + $j] ^= $this->gexp($vi + $this->glog($vj)); + } + + } + + $this->setNum($n); + + return $this; + } + + /** + * @param array $e + * + * @return \chillerlan\QRCode\Helpers\Polynomial + */ + public function mod(array $e):Polynomial{ + $n = $this->num; + + if(count($n) - count($e) < 0){ + return $this; + } + + $ratio = $this->glog($n[0]) - $this->glog($e[0]); + + foreach($e as $i => $v){ + $n[$i] ^= $this->gexp($this->glog($v) + $ratio); + } + + $this->setNum($n)->mod($e); + + return $this; + } + + /** + * @param int $n + * + * @return int + * @throws \chillerlan\QRCode\QRCodeException + */ + public function glog(int $n):int { + + if($n < 1){ + throw new QRCodeException('log('.$n.')'); + } + + return Polynomial::table[$n][1]; + } + + /** + * @param int $n + * + * @return int + */ + public function gexp(int $n):int { + + if($n < 0){ + $n += 255; + } + elseif($n >= 256){ + $n -= 255; + } + + return Polynomial::table[$n][0]; + } + +} diff --git a/src/Output/QRImage.php b/src/Output/QRImage.php index e37c1ca2c..1d9896996 100644 --- a/src/Output/QRImage.php +++ b/src/Output/QRImage.php @@ -20,95 +20,77 @@ use chillerlan\QRCode\QRCode; class QRImage extends QROutputAbstract{ /** - * @var string - */ - protected $optionsInterface = QRImageOptions::class; - - /** - * @var array - */ - protected $types = [ - QRCode::OUTPUT_IMAGE_GIF, - QRCode::OUTPUT_IMAGE_JPG, - QRCode::OUTPUT_IMAGE_PNG, - ]; - - /** + * @todo * @return string */ public function dump():string { - // clamp input (@todo: determine sane values!) - $this->options->pixelSize = max(1, min(25, (int)$this->options->pixelSize)); - $this->options->marginSize = max(0, min(25, (int)$this->options->marginSize)); - - foreach(['fgRed', 'fgGreen', 'fgBlue', 'bgRed', 'bgGreen', 'bgBlue'] as $val){ - $this->options->{$val} = max(0, min(255, (int)$this->options->{$val})); - } - - $length = $this->pixelCount * $this->options->pixelSize + $this->options->marginSize * 2; + $length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale; $image = imagecreatetruecolor($length, $length); - $foreground = imagecolorallocate($image, $this->options->fgRed, $this->options->fgGreen, $this->options->fgBlue); - $background = imagecolorallocate($image, $this->options->bgRed, $this->options->bgGreen, $this->options->bgBlue); + $background = imagecolorallocate($image, 255, 255, 255); - if((bool)$this->options->transparent && $this->options->type !== QRCode::OUTPUT_IMAGE_JPG){ + if((bool)$this->options->imageTransparent && $this->options->outputType !== QRCode::OUTPUT_IMAGE_JPG){ imagecolortransparent($image, $background); } imagefilledrectangle($image, 0, 0, $length, $length, $background); - foreach($this->matrix as $r => $row){ + foreach($this->matrix->matrix() as $r => $row){ foreach($row as $c => $pixel){ - if($pixel){ - imagefilledrectangle($image, - $this->options->marginSize + $c * $this->options->pixelSize, - $this->options->marginSize + $r * $this->options->pixelSize, - $this->options->marginSize + ($c + 1) * $this->options->pixelSize - 1, - $this->options->marginSize + ($r + 1) * $this->options->pixelSize - 1, - $foreground); - } + list($red, $green, $blue) = $this->options->moduleValues[$pixel]; + + imagefilledrectangle( + $image, + $c * $this->options->scale, + $r * $this->options->scale, + ($c + 1) * $this->options->scale - 1, + ($r + 1) * $this->options->scale - 1, + imagecolorallocate($image, $red, $green, $blue) + ); + } } ob_start(); - switch($this->options->type){ - case QRCode::OUTPUT_IMAGE_JPG: - // @codeCoverageIgnoreStart - imagejpeg( - $image, - $this->options->cachefile, - in_array($this->options->jpegQuality, range(0, 100), true) - ? $this->options->jpegQuality - : 85 - ); - break; - // @codeCoverageIgnoreEnd - case QRCode::OUTPUT_IMAGE_GIF: /** Actually, it's pronounced "DJIFF". *hides* */ - imagegif( - $image, - $this->options->cachefile - ); - break; - case QRCode::OUTPUT_IMAGE_PNG: - default: - imagepng( - $image, - $this->options->cachefile, - in_array($this->options->pngCompression, range(-1, 9), true) - ? $this->options->pngCompression - : -1 - ); - } - + $this->{$this->options->outputType ?? 'png'}($image); $imageData = ob_get_contents(); imagedestroy($image); + ob_end_clean(); - if((bool)$this->options->base64){ - $imageData = 'data:image/'.$this->options->type.';base64,'.base64_encode($imageData); + if((bool)$this->options->imageBase64){ + $imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData); } return $imageData; } + protected function png(&$image){ + imagepng( + $image, + $this->options->cachefile, + in_array($this->options->pngCompression, range(-1, 9), true) + ? $this->options->pngCompression + : -1 + ); + + } + + /** + * Jiff - like... JitHub! + */ + protected function gif(&$image){ + imagegif($image, $this->options->cachefile); + } + + protected function jpg(&$image){ + imagejpeg( + $image, + $this->options->cachefile, + in_array($this->options->jpegQuality, range(0, 100), true) + ? $this->options->jpegQuality + : 85 + ); + } + } diff --git a/src/Output/QRImageOptions.php b/src/Output/QRImageOptions.php deleted file mode 100644 index 759e2a733..000000000 --- a/src/Output/QRImageOptions.php +++ /dev/null @@ -1,101 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode\Output; - -use chillerlan\QRCode\QRCode; - -/** - * @property string $type - * @property bool $base64 - * @property int $pixelSize - * @property int $marginSize - * @property bool $transparent - * @property int $fgRed - * @property int $fgGreen - * @property int $fgBlue - * @property int $bgRed - * @property int $bgGreen - * @property int $bgBlue - * @property int $pngCompression - * @property int $jpegQuality - */ -class QRImageOptions extends QROutputOptionsAbstract{ - - /** - * @var string - */ - protected $type = QRCode::OUTPUT_IMAGE_PNG; - - /** - * @var bool - */ - protected $base64 = true; - - /** - * @var int - */ - protected $pixelSize = 5; - - /** - * @var int - */ - protected $marginSize = 5; - - /** - * not supported by jpg - * - * @var bool - */ - protected $transparent = true; - - /** - * @var int - */ - protected $fgRed = 0; - - /** - * @var int - */ - protected $fgGreen = 0; - - /** - * @var int - */ - protected $fgBlue = 0; - - /** - * @var int - */ - protected $bgRed = 255; - - /** - * @var int - */ - protected $bgGreen = 255; - - /** - * @var int - */ - protected $bgBlue = 255; - - /** - * @var int - */ - protected $pngCompression = -1; - - /** - * @var int - */ - protected $jpegQuality = 85; - -} diff --git a/src/Output/QRMarkup.php b/src/Output/QRMarkup.php index 116efec92..7f7eb3147 100644 --- a/src/Output/QRMarkup.php +++ b/src/Output/QRMarkup.php @@ -19,24 +19,11 @@ use chillerlan\QRCode\QRCode; */ class QRMarkup extends QROutputAbstract{ - /** - * @var string - */ - protected $optionsInterface = QRMarkupOptions::class; - - /** - * @var array - */ - protected $types = [ - QRCode::OUTPUT_MARKUP_HTML, - QRCode::OUTPUT_MARKUP_SVG, - ]; - /** * @return string */ public function dump() { - switch($this->options->type){ + switch($this->options->outputType){ case QRCode::OUTPUT_MARKUP_SVG : return $this->toSVG(); case QRCode::OUTPUT_MARKUP_HTML: default: @@ -50,22 +37,14 @@ class QRMarkup extends QROutputAbstract{ protected function toHTML(){ $html = ''; - foreach($this->matrix as $row){ - // in order to not bloat the output too much, we use the shortest possible valid HTML tags + foreach($this->matrix->matrix() as $row){ $html .= '<'.$this->options->htmlRowTag.'>'; - foreach($row as $col){ - $tag = $col - ? 'b' // dark - : 'i'; // light - - $html .= '<'.$tag.'>'; - } - - if(!(bool)$this->options->htmlOmitEndTag){ - $html .= 'options->htmlRowTag.'>'; + foreach($row as $pixel){ + $html .= ''; } + $html .= (bool)$this->options->htmlOmitEndTag ? 'options->htmlRowTag.'>' : ''; $html .= $this->options->eol; } @@ -84,42 +63,45 @@ class QRMarkup extends QROutputAbstract{ * @return string|bool */ protected function toSVG(){ - $length = $this->pixelCount * $this->options->pixelSize + $this->options->marginSize * 2; - $class = $this->options->cssClass ?: hash('crc32', microtime(true)); + $length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale; // svg header - $svg = ''.$this->options->eol. - ''.$this->options->eol; + $svg = ''.$this->options->eol. + ''.$this->options->eol; - // svg body - foreach($this->matrix as $r => $row){ - //we'll combine active blocks within a single row as a lightweight compression technique - $from = -1; - $count = 0; + // @todo: optimize -> see https://github.com/alexeyten/qr-image/blob/master/lib/vector.js + foreach($this->options->moduleValues as $key => $value){ - foreach($row as $c => $pixel){ - if($pixel){ - $count++; + // svg body + foreach($this->matrix->matrix() as $y => $row){ + //we'll combine active blocks within a single row as a lightweight compression technique + $from = -1; + $count = 0; - if($from < 0){ - $from = $c; + foreach($row as $x => $pixel){ + if($pixel === $key){ + $count++; + + if($from < 0){ + $from = $x; + } + } + elseif($from >= 0){ + $svg .= ''.$this->options->eol; + + // reset count + $from = -1; + $count = 0; } } - elseif($from >= 0){ - $svg .= ''.$this->options->eol; - // reset count - $from = -1; - $count = 0; + // close off the row, if applicable + if($from >= 0){ + $svg .= ''.$this->options->eol; } } - - // close off the row, if applicable - if($from >= 0){ - $svg .= ''.$this->options->eol; - } } // close svg diff --git a/src/Output/QRMarkupOptions.php b/src/Output/QRMarkupOptions.php deleted file mode 100644 index aca2bd98b..000000000 --- a/src/Output/QRMarkupOptions.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode\Output; - -use chillerlan\QRCode\QRCode; - -/** - * @property string $type - * @property string $htmlRowTag - * @property bool $htmlOmitEndTag - * @property string $fgColor - * @property string $bgColor - * @property int $pixelSize - * @property int $marginSize - * @property string $cssClass - */ -class QRMarkupOptions extends QROutputOptionsAbstract{ - - /** - * QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG - * - * @var string - */ - protected $type = QRCode::OUTPUT_MARKUP_SVG; - - /** - * the shortest available semanically correct row (block) tag to not bloat the output - * - * @var string - */ - protected $htmlRowTag = 'p'; - - /** - * the closing tag may be omitted (moar bloat!) - * - * @var bool - */ - protected $htmlOmitEndTag = true; - - /** - * foreground color - * - * @var string - */ - protected $fgColor = '#000'; - - /** - * background color - * - * @var string - */ - protected $bgColor = '#fff'; - - /** - * size of a QR code pixel - * - * @var int - */ - protected $pixelSize = 5; - - /** - * margin around the QR code - * - * @var int - */ - protected $marginSize = 5; - - /** - * a common css class - * - * @var string - */ - protected $cssClass; - -} diff --git a/src/Output/QROutputAbstract.php b/src/Output/QROutputAbstract.php index d2af4d422..131dd956f 100644 --- a/src/Output/QROutputAbstract.php +++ b/src/Output/QROutputAbstract.php @@ -12,30 +12,24 @@ namespace chillerlan\QRCode\Output; +use chillerlan\QRCode\{ + Data\QRMatrix, QROptions +}; + /** * */ abstract class QROutputAbstract implements QROutputInterface{ /** - * @var string - */ - protected $optionsInterface; - - /** - * @var array - */ - protected $types; - - /** - * @var array + * @param \chillerlan\QRCode\Data\QRMatrix $matrix */ protected $matrix; /** * @var int */ - protected $pixelCount; + protected $moduleCount; /** * @var object @@ -43,42 +37,21 @@ abstract class QROutputAbstract implements QROutputInterface{ protected $options; /** - * @var \chillerlan\QRCode\Output\QROutputOptionsAbstract $outputOptions - * @throws \chillerlan\QRCode\Output\QRCodeOutputException - */ - public function __construct(QROutputOptionsAbstract $outputOptions = null){ - $this->options = $outputOptions; - - if($this->optionsInterface && !$this->options instanceof $this->optionsInterface){ - $this->options = new $this->optionsInterface; - } - - if(is_array($this->types) && !in_array($this->options->type, $this->types , true)){ - throw new QRCodeOutputException('Invalid output type!'); - } - - } - - /** - * @param array $matrix + * @param \chillerlan\QRCode\QROptions $options + * @param \chillerlan\QRCode\Data\QRMatrix $matrix * - * @return \chillerlan\QRCode\Output\QROutputInterface * @throws \chillerlan\QRCode\Output\QRCodeOutputException */ - public function setMatrix(array $matrix):QROutputInterface { - $this->pixelCount = count($matrix); + public function __construct(QROptions $options, QRMatrix $matrix){ + $this->options = $options; - // specify valid range? - if($this->pixelCount < 2 - || !isset($matrix[$this->pixelCount - 1]) - || $this->pixelCount !== count($matrix[$this->pixelCount - 1]) - ){ + $this->moduleCount = $matrix->size(); + + if($this->moduleCount < 21){ // minimum QR modules @todo: quet zone throw new QRCodeOutputException('Invalid matrix!'); } $this->matrix = $matrix; - - return $this; } /** @@ -93,7 +66,7 @@ abstract class QROutputAbstract implements QROutputInterface{ return (bool)file_put_contents($this->options->cachefile, $data); } catch(\Exception $e){ - throw new QRCodeOutputException('Could not write to cache file: '.$e->getMessage()); + throw new QRCodeOutputException('Could not write data to cache file: '.$e->getMessage()); } } diff --git a/src/Output/QROutputInterface.php b/src/Output/QROutputInterface.php index 9befe612d..5b6c92645 100644 --- a/src/Output/QROutputInterface.php +++ b/src/Output/QROutputInterface.php @@ -22,12 +22,4 @@ interface QROutputInterface{ */ public function dump(); - /** - * @param array $matrix - * - * @return \chillerlan\QRCode\Output\QROutputInterface - * @throws \chillerlan\QRCode\Output\QRCodeOutputException - */ - public function setMatrix(array $matrix):QROutputInterface; - } diff --git a/src/Output/QROutputOptionsAbstract.php b/src/Output/QROutputOptionsAbstract.php deleted file mode 100644 index a1f548c14..000000000 --- a/src/Output/QROutputOptionsAbstract.php +++ /dev/null @@ -1,48 +0,0 @@ - - * @copyright 2016 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode\Output; - -use chillerlan\QRCode\Container; - -/** - * @property string $type - * @property string $eol - * @property string $cachefile - */ -abstract class QROutputOptionsAbstract{ - use Container; - - /** - * Output type, determined by the used interface - * - * QRCode::OUTPUT_... - * - * @var string - */ - protected $type; - - /** - * newline string - * - * @var string - */ - protected $eol = PHP_EOL; - - /** - * /path/to/cache.file - * - * @var string - */ - protected $cachefile; - -} diff --git a/src/Output/QRString.php b/src/Output/QRString.php index d0b2e2202..5d7a8e041 100644 --- a/src/Output/QRString.php +++ b/src/Output/QRString.php @@ -19,19 +19,12 @@ use chillerlan\QRCode\QRCode; */ class QRString extends QROutputAbstract{ - protected $optionsInterface = QRStringOptions::class; - - protected $types = [ - QRCode::OUTPUT_STRING_TEXT, - QRCode::OUTPUT_STRING_JSON, - ]; - /** * @return string */ public function dump():string { - switch($this->options->type){ + switch($this->options->outputType){ case QRCode::OUTPUT_STRING_TEXT: return $this->toString(); case QRCode::OUTPUT_STRING_JSON: default: @@ -46,7 +39,7 @@ class QRString extends QROutputAbstract{ protected function toString():string { $str = ''; - foreach($this->matrix as $row){ + foreach($this->matrix->matrix() as $row){ foreach($row as $col){ $str .= $col ? $this->options->textDark diff --git a/src/Output/QRStringOptions.php b/src/Output/QRStringOptions.php deleted file mode 100644 index b4bf48632..000000000 --- a/src/Output/QRStringOptions.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode\Output; - -use chillerlan\QRCode\QRCode; - -/** - * @property string $type - * @property string $textDark - * @property string $textLight - */ -class QRStringOptions extends QROutputOptionsAbstract{ - - /** - * QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON - * - * @var string - */ - protected $type = QRCode::OUTPUT_STRING_JSON; - - /** - * string substitute for dark - * - * @var string - */ - protected $textDark = '๐Ÿ”ด'; - - /** - * string substitute for light - * - * @var string - */ - protected $textLight = 'โญ•'; - -} diff --git a/src/Polynomial.php b/src/Polynomial.php deleted file mode 100644 index e2a092ea2..000000000 --- a/src/Polynomial.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode; - -/** - * - */ -class Polynomial{ - - /** - * @var array - */ - public $num = []; - - /** - * @var array - */ - protected $expTable = []; - - /** - * @var array - */ - protected $logTable = []; - - /** - * Polynomial constructor. - * - * @param array $num - * @param int $shift - */ - public function __construct(array $num = [1], int $shift = 0){ - $this->setNum($num, $shift)->setTables(); - } - - /** - * @param array $num - * @param int $shift - * - * @return \chillerlan\QRCode\Polynomial - */ - public function setNum(array $num, int $shift = 0):Polynomial { - $offset = 0; - $numCount = count($num); - - while($offset < $numCount && $num[$offset] === 0){ - $offset++; - } - - $this->num = array_fill(0, $numCount - $offset + $shift, 0); - - for($i = 0; $i < $numCount - $offset; $i++){ - $this->num[$i] = $num[$i + $offset]; - } - - return $this; - } - - /** - * @return void - */ - protected function setTables(){ - $this->expTable = $this->logTable = array_fill(0, 256, 0); - - for($i = 0; $i < 8; $i++){ - $this->expTable[$i] = 1 << $i; - } - - for($i = 8; $i < 256; $i++){ - $this->expTable[$i] = $this->expTable[$i - 4]^$this->expTable[$i - 5]^$this->expTable[$i - 6]^$this->expTable[$i - 8]; - } - - for($i = 0; $i < 255; $i++){ - $this->logTable[$this->expTable[$i]] = $i; - } - - } - - /** - * @param array $e - * - * @return void - */ - public function multiply(array $e){ - $n = array_fill(0, count($this->num) + count($e) - 1, 0); - - foreach($this->num as $i => &$vi){ - foreach($e as $j => &$vj){ - $n[$i + $j] ^= $this->gexp($this->glog($vi) + $this->glog($vj)); - } - } - - $this->setNum($n); - } - - /** - * @param array $e - * - * @return void - */ - public function mod(array $e){ - $n = $this->num; - - if(count($n) - count($e) < 0){ - return; - } - - $ratio = $this->glog($n[0]) - $this->glog($e[0]); - - foreach($e as $i => &$v){ - $n[$i] ^= $this->gexp($this->glog($v) + $ratio); - } - - $this->setNum($n)->mod($e); - } - - /** - * @param int $n - * - * @return int - * @throws \chillerlan\QRCode\QRCodeException - */ - public function glog(int $n):int { - - if($n < 1){ - throw new QRCodeException('log('.$n.')'); - } - - return $this->logTable[$n]; - } - - /** - * @param int $n - * - * @return int - */ - public function gexp(int $n):int { - - if($n < 0){ - $n += 255; - } - elseif($n >= 256){ - $n -= 255; - } - - return $this->expTable[$n]; - } - -} diff --git a/src/QRCode.php b/src/QRCode.php index c88a1dd2a..ab26a1f4a 100644 --- a/src/QRCode.php +++ b/src/QRCode.php @@ -12,120 +12,302 @@ namespace chillerlan\QRCode; -use chillerlan\QRCode\Output\QROutputInterface; +use chillerlan\QRCode\Data\{ + AlphaNum, Byte, Kanji, MaskPatternTester, Number, QRCodeDataException, QRDataInterface, QRMatrix +}; +use chillerlan\QRCode\Output\{ + QRCodeOutputException, QRImage, QRMarkup, QROutputInterface, QRString +}; +use chillerlan\Traits\ClassLoader; /** * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php + * @link http://www.qrcode.com/en/codes/model12.html * @link http://www.thonky.com/qr-code-tutorial/ */ class QRCode{ + use ClassLoader; /** * API constants */ - const OUTPUT_STRING_TEXT = 'txt'; - const OUTPUT_STRING_JSON = 'json'; + const OUTPUT_MARKUP_HTML = 'html'; + const OUTPUT_MARKUP_SVG = 'svg'; +# const OUTPUT_MARKUP_XML = 'xml'; // anyone? - const OUTPUT_MARKUP_HTML = 'html'; - const OUTPUT_MARKUP_SVG = 'svg'; -# const OUTPUT_MARKUP_XML = 'xml'; // anyone? + const OUTPUT_IMAGE_PNG = 'png'; + const OUTPUT_IMAGE_JPG = 'jpg'; + const OUTPUT_IMAGE_GIF = 'gif'; - const OUTPUT_IMAGE_PNG = 'png'; - const OUTPUT_IMAGE_JPG = 'jpg'; - const OUTPUT_IMAGE_GIF = 'gif'; + const OUTPUT_STRING_JSON = 'json'; + const OUTPUT_STRING_TEXT = 'txt'; - const ERROR_CORRECT_LEVEL_L = 1; // 7%. - const ERROR_CORRECT_LEVEL_M = 0; // 15%. - const ERROR_CORRECT_LEVEL_Q = 3; // 25%. - const ERROR_CORRECT_LEVEL_H = 2; // 30%. + const VERSION_AUTO = -1; + const MASK_PATTERN_AUTO = -1; - // max bits @ ec level L:07 M:15 Q:25 H:30 % - const TYPE_01 = 1; // 152 128 104 72 - const TYPE_02 = 2; // 272 224 176 128 - const TYPE_03 = 3; // 440 352 272 208 - const TYPE_04 = 4; // 640 512 384 288 - const TYPE_05 = 5; // 864 688 496 368 - const TYPE_06 = 6; // 1088 864 608 480 - const TYPE_07 = 7; // 1248 992 704 528 - const TYPE_08 = 8; // 1552 1232 880 688 - const TYPE_09 = 9; // 1856 1456 1056 800 - const TYPE_10 = 10; // 2192 1728 1232 976 + const ECC_L = 0b01; // 7%. + const ECC_M = 0b00; // 15%. + const ECC_Q = 0b11; // 25%. + const ECC_H = 0b10; // 30%. + + const DATA_NUMBER = 0b0001; + const DATA_ALPHANUM = 0b0010; + const DATA_BYTE = 0b0100; + const DATA_KANJI = 0b1000; + + const ECC_MODES = [ + QRCode::ECC_L => 0, + QRCode::ECC_M => 1, + QRCode::ECC_Q => 2, + QRCode::ECC_H => 3, + ]; + + const DATA_MODES = [ + self::DATA_NUMBER => 0, + self::DATA_ALPHANUM => 1, + self::DATA_BYTE => 2, + self::DATA_KANJI => 3, + ]; + + const OUTPUT_MODES = [ + QRMarkup::class => [ + self::OUTPUT_MARKUP_SVG, + self::OUTPUT_MARKUP_HTML, + ], + QRImage::class => [ + self::OUTPUT_IMAGE_PNG, + self::OUTPUT_IMAGE_GIF, + self::OUTPUT_IMAGE_JPG, + ], + QRString::class => [ + self::OUTPUT_STRING_JSON, + self::OUTPUT_STRING_TEXT, + ] + ]; /** - * @var int + * @var \chillerlan\QRCode\QROptions */ - protected $typeNumber; + protected $options; /** - * @var int + * @var \chillerlan\QRCode\Data\QRDataInterface */ - protected $errorCorrectLevel; - - /** - * @var \chillerlan\QRCode\Output\QROutputInterface - */ - protected $qrOutputInterface; - - /** - * @var string - */ - protected $data; + protected $dataInterface; /** * QRCode constructor. * - * @param string $data - * @param \chillerlan\QRCode\Output\QROutputInterface $output - * @param \chillerlan\QRCode\QROptions|null $options + * @param \chillerlan\QRCode\QROptions|null $options */ - public function __construct($data, QROutputInterface $output, QROptions $options = null){ - $this->qrOutputInterface = $output; + public function __construct(QROptions $options = null){ + mb_internal_encoding('UTF-8'); - $this->setData($data, $options); + $this->setOptions($options ?? new QROptions); } /** - * @param string $data - * @param \chillerlan\QRCode\QROptions|null $options + * @param \chillerlan\QRCode\QROptions $options * * @return \chillerlan\QRCode\QRCode * @throws \chillerlan\QRCode\QRCodeException */ - public function setData(string $data, QROptions $options = null):QRCode { + public function setOptions(QROptions $options):QRCode{ - if(!$options instanceof QROptions){ - $options = new QROptions; + if(!array_key_exists(QRCode::ECC_MODES[$options->eccLevel], QRCode::ECC_MODES)){ + throw new QRCodeException('Invalid error correct level: '.$options->eccLevel); } - $this->data = trim($data); - $this->errorCorrectLevel = (int)$options->errorCorrectLevel; - $this->typeNumber = (int)$options->typeNumber; + $options->version = (int)$options->version; - if(empty($this->data)){ - throw new QRCodeException('No data given.'); - } + // clamp min/max version number + $options->versionMin = (int)min($options->versionMin, $options->versionMax); + $options->versionMax = (int)max($options->versionMin, $options->versionMax); - if(!in_array($this->errorCorrectLevel, range(0, 3), true)){ - throw new QRCodeException('Invalid error correct level: '.$this->errorCorrectLevel); - } + $this->options = $options; return $this; } /** + * @param string $data + * * @return mixed */ - public function output(){ - $this->qrOutputInterface->setMatrix($this->getRawData()); - - return $this->qrOutputInterface->dump(); + public function render(string $data){ + return $this->initOutputInterface($data)->dump(); } /** - * @return array + * @param string $data + * + * @return \chillerlan\QRCode\Data\QRMatrix + * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public function getRawData():array { - return (new QRDataGenerator($this->data, $this->typeNumber, $this->errorCorrectLevel))->getRawData(); + public function getMatrix(string $data):QRMatrix { + $data = trim($data); + + if(empty($data)){ + throw new QRCodeDataException('QRCode::getMatrix() No data given.'); + } + + $this->dataInterface = $this->initDataInterface($data); + + $maskPattern = $this->options->maskPattern === self::MASK_PATTERN_AUTO + ? $this->getBestMaskPattern() + : max(7, min(0, (int)$this->options->maskPattern)); + + $matrix = $this + ->dataInterface + ->initMatrix($maskPattern) + ; + + if((bool)$this->options->addQuietzone){ + $matrix->setQuietZone($this->options->quietzoneSize); + } + + return $matrix; + } + + /** + * @return int + */ + protected function getBestMaskPattern():int{ + $penalties = []; + + $tester = new MaskPatternTester; + + for($testPattern = 0; $testPattern < 8; $testPattern++){ + $matrix = $this + ->dataInterface + ->initMatrix($testPattern, true); + + $tester->setMatrix($matrix); + + $penalties[$testPattern] = $tester->testPattern(); + } + + return array_search(min($penalties), $penalties, true); + } + + /** + * @param string $data + * + * @return \chillerlan\QRCode\Data\QRDataInterface + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + protected function initDataInterface(string $data):QRDataInterface{ + + $DATA_MODES = [ + Number::class => 'Number', + AlphaNum::class => 'AlphaNum', + Kanji::class => 'Kanji', + Byte::class => 'Byte', + ]; + + foreach($DATA_MODES as $dataInterface => $mode){ + + if(call_user_func_array([$this, 'is'.$mode], [$data]) === true){ + return $this->loadClass($dataInterface, QRDataInterface::class, $this->options, $data); + } + + } + + throw new QRCodeDataException('invalid data type'); + } + + /** + * @param string $data + * + * @return \chillerlan\QRCode\Output\QROutputInterface + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function initOutputInterface(string $data):QROutputInterface{ + + foreach(self::OUTPUT_MODES as $outputInterface => $modes){ + + if(in_array($this->options->outputType, $modes, true)){ + return $this->loadClass($outputInterface, QROutputInterface::class, $this->options, $this->getMatrix($data)); + } + + } + + throw new QRCodeOutputException('invalid output type'); + } + + /** + * @param string $string + * + * @return bool + */ + public function isNumber(string $string):bool { + $len = strlen($string); + + for($i = 0; $i < $len; $i++){ + $c = ord($string[$i]); + + if(!(ord('0') <= $c && $c <= ord('9'))){ + return false; + } + } + + return true; + } + + /** + * @param string $string + * + * @return bool + */ + public function isAlphaNum(string $string):bool { + $len = strlen($string); + + for($i = 0; $i < $len; $i++){ + $c = ord($string[$i]); + + if( + !(ord('0') <= $c && $c <= ord('9')) + && !(ord('A') <= $c && $c <= ord('Z')) + && strpos(' $%*+-./:', $string[$i]) === false + ){ + return false; + } + } + + return true; + } + + /** + * @param string $string + * + * @return bool + */ + public function isKanji(string $string):bool { + $i = 0; + $len = strlen($string); + + while($i + 1 < $len){ + $c = ((0xff&ord($string[$i])) << 8)|(0xff&ord($string[$i + 1])); + + if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){ + return false; + } + + $i += 2; + } + + return !($i < $len); + } + + /** + * a dummy + * + * @param $data + * + * @return bool + */ + protected function isByte(string $data):bool{ + return !empty($data); } } diff --git a/src/QRDataGenerator.php b/src/QRDataGenerator.php deleted file mode 100644 index bc8baa7ca..000000000 --- a/src/QRDataGenerator.php +++ /dev/null @@ -1,989 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode; - -use chillerlan\QRCode\Data\{ - AlphaNum, Byte, Kanji, Number, QRDataInterface -}; - -/** - * const ALL THE THINGS! ~John Carmack - */ -class QRDataGenerator{ - - const RSBLOCK = [ - QRCode::ERROR_CORRECT_LEVEL_L => 0, - QRCode::ERROR_CORRECT_LEVEL_M => 1, - QRCode::ERROR_CORRECT_LEVEL_Q => 2, - QRCode::ERROR_CORRECT_LEVEL_H => 3, - ]; - - const DATA_INTERFACES = [ - QRDataInterface::MODE_ALPHANUM => AlphaNum::class, - QRDataInterface::MODE_BYTE => Byte::class, - QRDataInterface::MODE_KANJI => Kanji::class, - QRDataInterface::MODE_NUMBER => Number::class, - ]; - - const BLOCK_TABLE = [ - // 1 - [1, 26, 19], // L - [1, 26, 16], // M - [1, 26, 13], // Q - [1, 26, 9], // H - // 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], - ]; - - const PATTERN_POSITION = [ - [], - [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], - ]; - - const MAX_BITS = [ - QRCode::TYPE_01 => [128, 152, 72, 104], - QRCode::TYPE_02 => [224, 272, 128, 176], - QRCode::TYPE_03 => [352, 440, 208, 272], - QRCode::TYPE_04 => [512, 640, 288, 384], - QRCode::TYPE_05 => [688, 864, 368, 496], - QRCode::TYPE_06 => [864, 1088, 480, 608], - QRCode::TYPE_07 => [992, 1248, 528, 704], - QRCode::TYPE_08 => [1232, 1552, 688, 880], - QRCode::TYPE_09 => [1456, 1856, 800, 1056], - QRCode::TYPE_10 => [1728, 2192, 976, 1232], - ]; - - const 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]], - ]; - - /** - * @var int - */ - protected $typeNumber; - - /** - * @var int - */ - protected $errorCorrectLevel; - - /** - * @var int - */ - protected $lostPoint; - - /** - * @var int - */ - protected $darkCount; - - /** - * @var float - */ - protected $minLostPoint; - - /** - * @var int - */ - protected $maskPattern; - - /** - * @var array - */ - protected $matrix = []; - - /** - * @var int - */ - protected $pixelCount = 0; - - /** - * @var \chillerlan\QRCode\Data\QRDataInterface - */ - protected $qrDataInterface; - - /** - * @var \chillerlan\QRCode\BitBuffer - */ - protected $bitBuffer; - - /** - * QRDataGenerator constructor. - * - * @param string $data - * @param int $typeNumber - * @param int $errorCorrectLevel - * - * @throws \chillerlan\QRCode\QRCodeException - */ - public function __construct(string $data, int $typeNumber, int $errorCorrectLevel){ - $this->typeNumber = $typeNumber; - $this->errorCorrectLevel = $errorCorrectLevel; - $this->bitBuffer = new BitBuffer; - - switch(true){ - case $this->isAlphaNum($data): - $mode = $this->isNumber($data) - ? QRDataInterface::MODE_NUMBER - : QRDataInterface::MODE_ALPHANUM; - break; - case $this->isKanji($data): - $mode = QRDataInterface::MODE_KANJI; - break; - default: - $mode = QRDataInterface::MODE_BYTE; - break; - } - - $qrDataInterface = self::DATA_INTERFACES[$mode]; - $this->qrDataInterface = new $qrDataInterface($data); - - if($this->typeNumber < 1 || $this->typeNumber > 10){ - $this->typeNumber = $this->getTypeNumber($this->qrDataInterface, $mode); - } - - } - - /** - * @return array - */ - public function getRawData():array{ - $this->minLostPoint = 0; - $this->maskPattern = 0; - - for($pattern = 0; $pattern <= 7; $pattern++){ - $this->testPattern($pattern); - } - - $this->getMatrix(false, $this->maskPattern); - - return $this->matrix; - } - - /** - * @param string $string - * - * @return bool - */ - public function isNumber(string $string):bool { - $len = strlen($string); - - for($i = 0; $i < $len; $i++){ - $chr = ord($string[$i]); - - if(!(ord('0') <= $chr && $chr <= ord('9'))){ - return false; - } - } - - return true; - } - - /** - * @param string $string - * - * @return bool - */ - public function isAlphaNum(string $string):bool { - $len = strlen($string); - - for($i = 0; $i < $len; $i++){ - $chr = ord($string[$i]); - - if( - !(ord('0') <= $chr && $chr <= ord('9')) - && !(ord('A') <= $chr && $chr <= ord('Z')) - && strpos(' $%*+-./:', $string[$i]) === false - ){ - return false; - } - } - - return true; - } - - /** - * @param string $string - * - * @return bool - */ - public function isKanji(string $string):bool { - - if(empty($string)){ - return false; - } - - $i = 0; - $len = strlen($string); - - while($i + 1 < $len){ - $c = ((0xff&ord($string[$i])) << 8)|(0xff&ord($string[$i + 1])); - - if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){ - return false; - } - - $i += 2; - } - - return !($i < $len); - } - - /** - * @param \chillerlan\QRCode\Data\QRDataInterface $qrDataInterface - * @param int $mode - * - * @return int - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function getTypeNumber(QRDataInterface $qrDataInterface, int $mode):int { - $length = $qrDataInterface->dataLength; - - if($qrDataInterface->mode === QRDataInterface::MODE_KANJI){ - $length = floor($length / 2); - } - - $maxTypenumber = QRCode::TYPE_10; - - $range = range(1, $maxTypenumber); - foreach($range as $type){ - - if($length <= $this->getMaxLength($type, $mode, $this->errorCorrectLevel)){ - $maxTypenumber = $type; - break; - } - - } - - return $maxTypenumber; - } - - /** - * @param int $typeNumber - * @param int $mode - * @param int $errorCorrectLevel - * - * @return int - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function getMaxLength(int $typeNumber, int $mode, int $errorCorrectLevel):int { - - if(!array_key_exists($errorCorrectLevel, self::RSBLOCK)){ - throw new QRCodeException('Invalid error correct level: '.$errorCorrectLevel); - } - - if(!array_key_exists($mode, QRDataInterface::MODE)){ - throw new QRCodeException('Invalid mode: '.$mode); - } - - return self::MAX_LENGTH[$typeNumber - 1][self::RSBLOCK[$errorCorrectLevel]][QRDataInterface::MODE[$mode]]; - } - - /** - * @param int $data - * - * @return int - */ - protected function getBCHTypeInfo(int $data):int { - $G15_MASK = (1 << 14)|(1 << 12)|(1 << 10)|(1 << 4)|(1 << 1); - $G15 = (1 << 10)|(1 << 8)|(1 << 5)|(1 << 4)|(1 << 2)|(1 << 1)|(1 << 0); - - return (($data << 10)|$this->getBCHT($data, 10, $G15))^$G15_MASK; - } - - /** - * @param int $data - * - * @return int - */ - protected function getBCHTypeNumber(int $data):int{ - $G18 = (1 << 12)|(1 << 11)|(1 << 10)|(1 << 9)|(1 << 8)|(1 << 5)|(1 << 2)|(1 << 0); - - return ($data << 12)|$this->getBCHT($data, 12, $G18); - } - - /** - * @param int $data - * @param int $bits - * @param int $mask - * - * @return int - */ - protected function getBCHT(int $data, int $bits, int $mask):int { - $digit = $data << $bits; - - while($this->getBCHDigit($digit) - $this->getBCHDigit($mask) >= 0){ - $digit ^= ($mask << ($this->getBCHDigit($digit) - $this->getBCHDigit($mask))); - } - - return $digit; - } - - /** - * @param int $data - * - * @return int - */ - protected function getBCHDigit(int $data):int { - $digit = 0; - - while($data !== 0){ - $digit++; - $data >>= 1; - } - - return $digit; - } - - /** - * @param int $typeNumber - * @param int $errorCorrectLevel - * - * @return array - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function getRSBlocks(int $typeNumber, int $errorCorrectLevel):array { - - if(!array_key_exists($errorCorrectLevel, self::RSBLOCK)){ - throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel); - } - - $rsBlock = self::BLOCK_TABLE[($typeNumber - 1) * 4 + self::RSBLOCK[$errorCorrectLevel]]; - $list = []; - $length = count($rsBlock) / 3; - - for($i = 0; $i < $length; $i++){ - for($j = 0; $j < $rsBlock[$i * 3 + 0]; $j++){ - $list[] = [$rsBlock[$i * 3 + 1], $rsBlock[$i * 3 + 2]]; - } - } - - return $list; - } - - /** - * @param array $range - * - * @return void - */ - protected function testLevel1(array $range){ - - foreach($range as $row){ - foreach($range as $col){ - $sameCount = 0; - - foreach([-1, 0, 1] as $rowRange){ - - if($row + $rowRange < 0 || $this->pixelCount <= $row + $rowRange){ - continue; - } - - foreach([-1, 0, 1] as $colRange){ - - if(($rowRange === 0 && $colRange === 0) || ($col + $colRange < 0 || $this->pixelCount <= $col + $colRange)){ - continue; - } - - if($this->matrix[$row + $rowRange][$col + $colRange] === $this->matrix[$row][$col]){ - $sameCount++; - } - - } - } - - if($sameCount > 5){ - $this->lostPoint += (3 + $sameCount - 5); - } - - } - } - - } - - /** - * @param array $range - * - * @return void - */ - protected function testLevel2(array $range){ - - foreach($range as $row){ - foreach($range as $col){ - $count = 0; - - if( - $this->matrix[$row ][$col ] - || $this->matrix[$row ][$col + 1] - || $this->matrix[$row + 1][$col ] - || $this->matrix[$row + 1][$col + 1] - ){ - $count++; - } - - if($count === 0 || $count === 4){ - $this->lostPoint += 3; - } - - } - } - - } - - /** - * @param array $range1 - * @param array $range2 - * - * @return void - */ - protected function testLevel3(array $range1, array $range2){ - - foreach($range1 as $row){ - foreach($range2 as $col){ - - if( - $this->matrix[$row][$col ] - && !$this->matrix[$row][$col + 1] - && $this->matrix[$row][$col + 2] - && $this->matrix[$row][$col + 3] - && $this->matrix[$row][$col + 4] - && !$this->matrix[$row][$col + 5] - && $this->matrix[$row][$col + 6] - ){ - $this->lostPoint += 40; - } - - } - } - - foreach($range1 as $col){ - foreach($range2 as $row){ - - if( - $this->matrix[$row ][$col] - && !$this->matrix[$row + 1][$col] - && $this->matrix[$row + 2][$col] - && $this->matrix[$row + 3][$col] - && $this->matrix[$row + 4][$col] - && !$this->matrix[$row + 5][$col] - && $this->matrix[$row + 6][$col] - ){ - $this->lostPoint += 40; - } - - } - } - - } - - /** - * @param array $range - * - * @return void - */ - protected function testLevel4(array $range){ - - foreach($range as $col){ - foreach($range as $row){ - if($this->matrix[$row][$col]){ - $this->darkCount++; - } - } - } - - } - - /** - * @param int $pattern - * - * @return void - */ - protected function testPattern(int $pattern){ - $this->getMatrix(true, $pattern); - $this->lostPoint = 0; - $this->darkCount = 0; - - $range = range(0, $this->pixelCount - 1); - - $this->testLevel1($range); - $this->testLevel2(range(0, $this->pixelCount - 2)); - $this->testLevel3($range, range(0, $this->pixelCount - 7)); - $this->testLevel4($range); - - $this->lostPoint += (abs(100 * $this->darkCount / $this->pixelCount / $this->pixelCount - 50) / 5) * 10; - - if($pattern === 0 || $this->minLostPoint > $this->lostPoint){ - $this->minLostPoint = $this->lostPoint; - $this->maskPattern = $pattern; - } - - } - - /** - * @param bool $test - * - * @return void - */ - protected function setTypeNumber(bool $test){ - $bits = $this->getBCHTypeNumber($this->typeNumber); - - $range = range(0, 17); - foreach($range as $i){ - $v = !$test && (($bits >> $i) & 1) === 1; - - $a = (int)floor($i / 3); - $b = $i % 3 + $this->pixelCount - 8 - 3; - - $this->matrix[$a][$b] = $v; - $this->matrix[$b][$a] = $v; - } - - } - - /** - * @param bool $test - * @param int $pattern - * - * @return void - */ - protected function setTypeInfo(bool $test, int $pattern){ - $this->setPattern(); - $bits = $this->getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern); - - $range = range(0, 14); - foreach($range as $i){ - $mod = !$test && (($bits >> $i) & 1) === 1; - - switch(true){ - case $i < 6: - $this->matrix[$i][8] = $mod; - break; - case $i < 8: - $this->matrix[$i + 1][8] = $mod; - break; - default: - $this->matrix[$this->pixelCount - 15 + $i][8] = $mod; - } - - switch(true){ - case $i < 8: - $this->matrix[8][$this->pixelCount - $i - 1] = $mod; - break; - case $i < 9: - $this->matrix[8][15 + 1 - $i - 1] = $mod; - break; - default: - $this->matrix[8][15 - $i - 1] = $mod; - } - - } - - $this->matrix[$this->pixelCount - 8][8] = !$test; - } - - /** - * @return void - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function createData(){ - $MAX_BITS = self::MAX_BITS[$this->typeNumber][$this->errorCorrectLevel]; - $PAD0 = 0xEC; - $PAD1 = 0x11; - - $this->bitBuffer - ->clear() - ->put($this->qrDataInterface->mode, 4) - ->put( - $this->qrDataInterface->mode === QRDataInterface::MODE_KANJI - ? floor($this->qrDataInterface->dataLength / 2) - : $this->qrDataInterface->dataLength, - $this->qrDataInterface->getLengthInBits($this->typeNumber) - ); - - $this->qrDataInterface->write($this->bitBuffer); - - if($this->bitBuffer->length > $MAX_BITS){ - throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)'); - } - - // end code. - if($this->bitBuffer->length + 4 <= $MAX_BITS){ - $this->bitBuffer->put(0, 4); - } - - // padding - while($this->bitBuffer->length % 8 !== 0){ - $this->bitBuffer->putBit(false); - } - - // padding - while(true){ - - if($this->bitBuffer->length >= $MAX_BITS){ - break; - } - - $this->bitBuffer->put($PAD0, 8); - - if($this->bitBuffer->length >= $MAX_BITS){ - break; - } - - $this->bitBuffer->put($PAD1, 8); - } - - } - - /** - * @return array - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function createBytes():array{ - $rsBlocks = $this->getRSBlocks($this->typeNumber, $this->errorCorrectLevel); - $rsBlockCount = count($rsBlocks); - $ecdata = array_fill(0, $rsBlockCount, null); - $dcdata = $ecdata; - $totalCodeCount = 0; - $maxDcCount = 0; - $maxEcCount = 0; - $offset = 0; - $index = 0; - - foreach($rsBlocks as $key => $block){ - $rsBlockTotal = $block[0]; - $rsBlockDataCount = $block[1]; - $maxDcCount = max($maxDcCount, $rsBlockDataCount); - $maxEcCount = max($maxEcCount, $rsBlockTotal - $rsBlockDataCount); - $dcdata[$key] = array_fill(0, $rsBlockDataCount, null); - - foreach($dcdata[$key] as $a => &$_dcdata){ - $bdata = $this->bitBuffer->buffer; - $_dcdata = 0xff & $bdata[$a + $offset]; - } - - $offset += $rsBlockDataCount; - - $rsPoly = new Polynomial; - $modPoly = new Polynomial; - - $blockrange = range(0, $rsBlockTotal - $rsBlockDataCount - 1); - - foreach($blockrange as $b){ - $modPoly->setNum([1, $modPoly->gexp($b)]); - $rsPoly->multiply($modPoly->num); - } - - $rsPolyCount = count($rsPoly->num); - - $modPoly->setNum($dcdata[$key], $rsPolyCount - 1)->mod($rsPoly->num); - - $ecdata[$key] = array_fill(0, $rsPolyCount - 1, null); - $add = count($modPoly->num) - count($ecdata[$key]); - - foreach($ecdata[$key] as $c => &$_ecdata){ - $modIndex = $c + $add; - $_ecdata = $modIndex >= 0 ? $modPoly->num[$modIndex] : 0; - } - - $totalCodeCount += $rsBlockTotal; - } - - $data = array_fill(0, $totalCodeCount, null); - $rsrange = range(0, $rsBlockCount - 1); - $dcrange = range(0, $maxDcCount - 1); - $ecrange = range(0, $maxEcCount - 1); - - foreach($dcrange as $x){ - foreach($rsrange as $j){ - if($x < count($dcdata[$j])){ - $data[$index++] = $dcdata[$j][$x]; - } - } - } - - foreach($ecrange as $y){ - foreach($rsrange as $k){ - if($y < count($ecdata[$k])){ - $data[$index++] = $ecdata[$k][$y]; - } - } - } - - return $data; - } - - /** - * @param int $pattern - * - * @return void - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function mapData(int $pattern){ - $this->createData(); - - $data = $this->createBytes(); - $inc = -1; - $row = $this->pixelCount - 1; - $bitIndex = 7; - $byteIndex = 0; - $dataCount = count($data); - - for($col = $this->pixelCount - 1; $col > 0; $col -= 2){ - - if($col === 6){ - $col--; - } - - while(true){ - foreach([0, 1] as $c){ - $_col = $col - $c; - - if($this->matrix[$row][$_col] === null){ - $dark = false; - - if($byteIndex < $dataCount){ - $dark = (($data[$byteIndex] >> $bitIndex) & 1) === 1; - } - - $a = $row + $_col; - $m = $row * $_col; - - $MASK_PATTERN = [ - 0 => $a % 2, - 1 => $row % 2, - 2 => $_col % 3, - 3 => $a % 3, - 4 => (floor($row / 2) + floor($_col / 3)) % 2, - 5 => $m % 2 + $m % 3, - 6 => ($m % 2 + $m % 3) % 2, - 7 => ($m % 3 + $a % 2) % 2, - ][$pattern]; - - if($MASK_PATTERN === 0){ - $dark = !$dark; - } - - $this->matrix[$row][$_col] = $dark; - - $bitIndex--; - - if($bitIndex === -1){ - $byteIndex++; - $bitIndex = 7; - } - - } - } - - $row += $inc; - - if($row < 0 || $this->pixelCount <= $row){ - $row -= $inc; - $inc = -$inc; - - break; - } - - } - } - - } - - /** - * @return void - */ - protected function setupPositionProbePattern(){ - $range = range(-1, 7); - - foreach([[0, 0], [$this->pixelCount - 7, 0], [0, $this->pixelCount - 7]] as $grid){ - $row = $grid[0]; - $col = $grid[1]; - - foreach($range as $r){ - foreach($range as $c){ - - if($row + $r <= -1 || $this->pixelCount <= $row + $r || $col + $c <= -1 || $this->pixelCount <= $col + $c){ - continue; - } - - $this->matrix[$row + $r][$col + $c] = - (0 <= $r && $r <= 6 && ($c === 0 || $c === 6)) - || (0 <= $c && $c <= 6 && ($r === 0 || $r === 6)) - || (2 <= $c && $c <= 4 && 2 <= $r && $r <= 4); - } - } - } - - } - - /** - * @return void - */ - protected function setupPositionAdjustPattern(){ - $range = self::PATTERN_POSITION[$this->typeNumber - 1]; - - foreach($range as $i => $posI){ - foreach($range as $j => $posJ){ - - if($this->matrix[$posI][$posJ] !== null){ - continue; - } - - for($row = -2; $row <= 2; $row++){ - for($col = -2; $col <= 2; $col++){ - $this->matrix[$posI + $row][$posJ + $col] = - $row === -2 || $row === 2 - || $col === -2 - || $col === 2 - || ($row === 0 && $col === 0); - } - } - - } - } - - } - - /** - * @return void - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function setPattern(){ - $this->setupPositionProbePattern(); - $this->setupPositionAdjustPattern(); - - // setupTimingPattern - $range = range(8, $this->pixelCount - 8 - 1); - foreach($range as $i){ - - if($this->matrix[$i][6] !== null){ - continue; // @codeCoverageIgnore - } - - $v = $i % 2 === 0; - - $this->matrix[$i][6] = $v; - $this->matrix[6][$i] = $v; - } - - } - - /** - * @param bool $test - * @param int $maskPattern - * - * @throws \chillerlan\QRCode\QRCodeException - */ - protected function getMatrix(bool $test, int $maskPattern){ - $this->pixelCount = $this->typeNumber * 4 + 17; - $this->matrix = array_fill(0, $this->pixelCount, array_fill(0, $this->pixelCount, null)); - - $this->setTypeInfo($test, $maskPattern); - - if($this->typeNumber >= 7){ - $this->setTypeNumber($test); - } - - $this->mapData($maskPattern); - } - -} diff --git a/src/QROptions.php b/src/QROptions.php index f05e148d2..752e36375 100644 --- a/src/QROptions.php +++ b/src/QROptions.php @@ -12,31 +12,243 @@ namespace chillerlan\QRCode; +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Traits\Container; + /** - * @property int $errorCorrectLevel - * @property int $typeNumber + * @property int $version + * @property int $versionMin + * @property int $versionMax + * @property int $eccLevel + * @property int $maskPattern + * @property bool $addQuietzone + * @property bool $quietzoneSize + * + * @property string $outputType + * @property string $cachefile + * + * @property string $eol + * @property int $scale + * + * @property string $cssClass + * @property string $htmlRowTag + * @property bool $htmlOmitEndTag + * @property string $svgFgColor + * @property string $svgBgColor + * + * @property string $textDark + * @property string $textLight + * + * @property bool $imageBase64 + * @property bool $imageTransparent + * @property int $pngCompression + * @property int $jpegQuality + * + * @property array $moduleValues */ class QROptions{ use Container; /** - * Error correct level + * QR Code version number * - * QRCode::ERROR_CORRECT_LEVEL_X where X is - * L, M, Q, H - * 7%, 15%, 25%, 30% + * [1 ... 40] or QRCode::VERSION_AUTO * * @var int */ - protected $errorCorrectLevel = QRCode::ERROR_CORRECT_LEVEL_M; + protected $version = QRCode::VERSION_AUTO; /** - * Type number - * - * QRCode::TYPE_XX where XX is 01 ... 10, null = auto + * Minimum QR version (if $version = QRCode::VERSION_AUTO) * * @var int */ - protected $typeNumber = null; + protected $versionMin = 1; + + /** + * Maximum QR version + * + * @var int + */ + protected $versionMax = 40; + + /** + * Error correct level + * + * QRCode::ECC_X where X is + * L => 7% + * M => 15% + * Q => 25% + * H => 30% + * + * @var int + */ + protected $eccLevel = QRCode::ECC_L; + + /** + * Mask Pattern to use + * + * [0...7] or QRCode::MASK_PATTERN_AUTO + * + * @var int + */ + protected $maskPattern = QRCode::MASK_PATTERN_AUTO; + + /** + * Add a "quiet zone" (margin) according to the QR code spec + * + * @var bool + */ + protected $addQuietzone = true; + + /** + * Size of the quiet zone + * + * internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules + * + * @var int + */ + protected $quietzoneSize; + + /** + * QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG + * QRCode::OUTPUT_IMAGE_XXX where XXX = PNG, GIF, JPG + * QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON + * + * @var string + */ + protected $outputType; + + /** + * /path/to/cache.file + * + * @var string + */ + protected $cachefile; + + + + /** + * newline string [HTML, SVG, TEXT] + * + * @var string + */ + protected $eol = PHP_EOL; + + /** + * size of a QR code pixel [SVG, IMAGE_*] + * HTML -> via CSS + * + * @var int + */ + protected $scale = 5; + + + + + /** + * a common css class + * + * @var string + */ + protected $cssClass; + + /** + * the shortest available semanically correct row (block) tag to not bloat the output + * + * @var string + */ + protected $htmlRowTag = 'p'; + + /** + * the closing tag may be omitted (moar bloat!) + * + * @var bool + */ + protected $htmlOmitEndTag = true; + + /** + * SVG foreground color + * + * @var string + */ + protected $svgFgColor = '#000'; + + /** + * SVG background color + * + * @var string + */ + protected $svgBgColor = '#fff'; + + + + /** + * string substitute for dark + * + * @var string + */ + protected $textDark = '๐Ÿ”ด'; + + /** + * string substitute for light + * + * @var string + */ + protected $textLight = 'โญ•'; + + + + /** + * @var bool + */ + protected $imageBase64 = true; + + /** + * not supported by jpg + * + * @var bool + */ + protected $imageTransparent = true; + + /** + * @var int + */ + protected $pngCompression = -1; + + /** + * @var int + */ + protected $jpegQuality = 85; + + /** + * Module values map + * + * HTML : #ABCDEF, cssname, rgb(), rgba()... + * IMAGE: [63, 127, 255] // R, G, B + * + * @var array + */ + protected $moduleValues = [ + // light + QRMatrix::M_DATA => false, // 4 + QRMatrix::M_FINDER => false, // 6 + QRMatrix::M_SEPARATOR => false, // 8 + QRMatrix::M_ALIGNMENT => false, // 10 + QRMatrix::M_TIMING => false, // 12 + QRMatrix::M_FORMAT => false, // 14 + QRMatrix::M_VERSION => false, // 16 + QRMatrix::M_QUIETZONE => false, // 18 + QRMatrix::M_TEST => false, // 255 + // dark + QRMatrix::M_DARKMODULE << 8 => true, // 512 + QRMatrix::M_DATA << 8 => true, // 1024 + QRMatrix::M_FINDER << 8 => true, // 1536 + QRMatrix::M_ALIGNMENT << 8 => true, // 2560 + QRMatrix::M_TIMING << 8 => true, // 3072 + QRMatrix::M_FORMAT << 8 => true, // 3584 + QRMatrix::M_VERSION << 8 => true, // 4096 + QRMatrix::M_TEST << 8 => true, // 65280 + ]; } diff --git a/tests/BitBufferTest.php b/tests/Data/BitBufferTest.php similarity index 63% rename from tests/BitBufferTest.php rename to tests/Data/BitBufferTest.php index b44e7616d..887f76122 100644 --- a/tests/BitBufferTest.php +++ b/tests/Data/BitBufferTest.php @@ -1,22 +1,25 @@ * @copyright 2015 Smiley * @license MIT */ -namespace chillerlan\QRCodeTest; +namespace chillerlan\QRCodeTest\Data; -use chillerlan\QRCode\BitBuffer; -use chillerlan\QRCode\Data\QRDataInterface; -use PHPUnit\Framework\TestCase; +use chillerlan\QRCode\Helpers\BitBuffer; +use chillerlan\QRCode\QRCode; +use chillerlan\QRCodeTest\QRTestAbstract; -class BitBufferTest extends TestCase{ +class BitBufferTest extends QRTestAbstract{ /** - * @var \chillerlan\QRCode\BitBuffer + * @var \chillerlan\QRCode\Helpers\BitBuffer */ protected $bitBuffer; @@ -26,10 +29,10 @@ class BitBufferTest extends TestCase{ public function bitProvider(){ return [ - [QRDataInterface::MODE_NUMBER, 16], - [QRDataInterface::MODE_ALPHANUM, 32], - [QRDataInterface::MODE_BYTE, 64], - [QRDataInterface::MODE_KANJI, 128], + [QRCode::DATA_NUMBER, 16], + [QRCode::DATA_ALPHANUM, 32], + [QRCode::DATA_BYTE, 64], + [QRCode::DATA_KANJI, 128], ]; } diff --git a/tests/Data/DataTest.php b/tests/Data/DataTest.php deleted file mode 100644 index e7bae1ac1..000000000 --- a/tests/Data/DataTest.php +++ /dev/null @@ -1,120 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeTest\Data; - -use chillerlan\QRCode\BitBuffer; -use chillerlan\QRCode\Data\AlphaNum; -use chillerlan\QRCode\Data\Byte; -use chillerlan\QRCode\Data\Kanji; -use chillerlan\QRCode\Data\Number; -use chillerlan\QRCode\Data\QRDataInterface; -use PHPUnit\Framework\TestCase; - -class DataTest extends TestCase{ - - /** - * @var \chillerlan\QRCode\BitBuffer - */ - protected $bitBuffer; - - /** - * @var \chillerlan\QRCode\Data\QRDataInterface - */ - protected $module; - - public function bitProviderMode(){ - return [ - [QRDataInterface::MODE_NUMBER, Number::class, '123456789'], - [QRDataInterface::MODE_NUMBER, Number::class, '1234567890'], - [QRDataInterface::MODE_NUMBER, Number::class, '12345678901'], - [QRDataInterface::MODE_ALPHANUM, AlphaNum::class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:'], - [QRDataInterface::MODE_BYTE, Byte::class, '#\\'], - [QRDataInterface::MODE_KANJI, Kanji::class, '่Œ—่ท'], - ]; - } - - protected function setUp(){ - $this->bitBuffer = new BitBuffer; - } - - /** - * @dataProvider bitProviderMode - */ - public function testMode($mode, $class, $data){ - $this->module = new $class($data); - $this->assertEquals($mode, $this->module->mode); - } - - - public function bitProviderWrite(){ - return [ - [Number::class, '123456789', [30, 220, 140, 84]], - [Number::class, '1234567890', [30, 220, 140, 84, 0]], - [Number::class, '12345678901', [30, 220, 140, 84, 8]], - [AlphaNum::class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', [57, 168, 165, 66, 174, 22, 122, 230, 95, 172, 81, 149, 180, 38, 178, 220, 28, 58, 11, 196, 88, 231, 40, 102, 87, 60, 237, 94, 99, 227, 108]], - [Byte::class, '#\\', [35, 92]], - [Kanji::class, '่Œ—่ท', [236, 100, 74, 18, 238]], - ]; - } - - /** - * @dataProvider bitProviderWrite - */ - public function testWrite($class, $data, $expected){ - $this->bitBuffer->clear(); - $this->module = new $class($data); - $this->module->write($this->bitBuffer); - - $this->assertSame($expected, $this->bitBuffer->buffer); - } - - /** - * @expectedException \chillerlan\QRCode\Data\QRCodeDataException - * @expectedExceptionMessage illegal char: 92 - */ - public function testAlphaNumCharException(){ - $this->bitBuffer->clear(); - $this->module = new AlphaNum('\\'); - $this->module->write($this->bitBuffer); - } - - /** - * @expectedException \chillerlan\QRCode\Data\QRCodeDataException - * @expectedExceptionMessage illegal char at 7 (50051) - */ - public function testKanjiCharExceptionA(){ - $this->bitBuffer->clear(); - $this->module = new Kanji('่Œ—่ทรƒ'); - $this->module->write($this->bitBuffer); - } - - /** - * @expectedException \chillerlan\QRCode\Data\QRCodeDataException - * @expectedExceptionMessage illegal char at 7 - */ - public function testKanjiCharExceptionB(){ - $this->bitBuffer->clear(); - $this->module = new Kanji('่Œ—่ท\\'); - $this->module->write($this->bitBuffer); - } - - /** - * @expectedException \chillerlan\QRCode\Data\QRCodeDataException - * @expectedExceptionMessage illegal char: 92 - */ - public function testNumberCharException(){ - $this->bitBuffer->clear(); - $this->module = new Number('\\'); - $this->module->write($this->bitBuffer); - } - - -} diff --git a/tests/PolynomialTest.php b/tests/Data/PolynomialTest.php similarity index 58% rename from tests/PolynomialTest.php rename to tests/Data/PolynomialTest.php index ca70877b1..56daaad87 100644 --- a/tests/PolynomialTest.php +++ b/tests/Data/PolynomialTest.php @@ -1,32 +1,33 @@ * @copyright 2015 Smiley * @license MIT */ -namespace chillerlan\QRCodeTest; +namespace chillerlan\QRCodeTest\Data; -use chillerlan\QRCode\Polynomial; -use PHPUnit\Framework\TestCase; +use chillerlan\QRCode\Helpers\Polynomial; +use chillerlan\QRCodeTest\QRTestAbstract; -class PolynomialTest extends TestCase{ +class PolynomialTest extends QRTestAbstract{ /** - * @var \chillerlan\QRCode\Polynomial + * @var \chillerlan\QRCode\Helpers\Polynomial */ protected $polynomial; protected function setUp(){ - $this->polynomial = new Polynomial; + $this->polynomial = new \chillerlan\QRCode\Helpers\Polynomial; } public function testGexp(){ - $this->assertEquals(142, $this->polynomial->gexp( -1)); + $this->assertEquals(142, $this->polynomial->gexp(-1)); $this->assertEquals(133, $this->polynomial->gexp(128)); $this->assertEquals(2, $this->polynomial->gexp(256)); } diff --git a/tests/Data/QRMatrixTest.php b/tests/Data/QRMatrixTest.php new file mode 100644 index 000000000..a3bd7bc4d --- /dev/null +++ b/tests/Data/QRMatrixTest.php @@ -0,0 +1,49 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Data; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\QRCodeTest\QRTestAbstract; + +class QRMatrixTest extends QRTestAbstract{ + + protected $FQCN = QRMatrix::class; + + public function testInstance(){ + $q = $this->reflection->newInstanceArgs([1, 0]); + $this->assertCount($q->size(), $q->matrix()); + $this->assertInstanceOf($this->FQCN, $q); + } + + /** + * @expectedException \chillerlan\QRCode\Data\QRCodeDataException + * @expectedExceptionMessage invalid QR Code version + */ + public function testInvalidVersionException(){ + $this->reflection->newInstanceArgs([42, 0]); + } + + /** + * @expectedException \chillerlan\QRCode\Data\QRCodeDataException + * @expectedExceptionMessage invalid ecc level + */ + public function testInvalidEccException(){ + $this->reflection->newInstanceArgs([1, 42]); + } + +/* public function testSetFinderPattern(){ + $q = $this->reflection->newInstanceArgs([1, 0]); + $q->setFinderPattern(); + var_dump($q->matrix()); + }*/ +} diff --git a/tests/Output/ImageTest.php b/tests/Output/ImageTest.php deleted file mode 100644 index da39eed8a..000000000 --- a/tests/Output/ImageTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeTest\Output; - -use chillerlan\QRCode\Output\QRImage; -use chillerlan\QRCode\Output\QRImageOptions; -use chillerlan\QRCode\QRCode; - -class ImageTest extends OutputTestAbstract{ - - protected $outputInterfaceClass = QRImage::class; - protected $outputOptionsClass = QRImageOptions::class; - - public function testOptions(){ - $this->assertEquals(QRCode::OUTPUT_IMAGE_PNG, $this->options->type); - $this->assertEquals(true, $this->options->base64); - } - - public function imageDataProvider(){ - return [ - [QRCode::OUTPUT_IMAGE_PNG, 'foobar', 'img1.png.uri'], - [QRCode::OUTPUT_IMAGE_GIF, 'foobar', 'img1.gif.uri'], - [QRCode::OUTPUT_IMAGE_JPG, 'foobar', 'img1.jpg.uri'], - [QRCode::OUTPUT_IMAGE_PNG, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'img2.png.uri'], - [QRCode::OUTPUT_IMAGE_GIF, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'img2.gif.uri'], - [QRCode::OUTPUT_IMAGE_JPG, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'img2.jpg.uri'], - ]; - } - - /** - * @dataProvider imageDataProvider - */ - public function testImageOutput($type, $data, $expected){ - $this->options->type = $type; - $output = (new QRCode($data, new $this->outputInterfaceClass($this->options)))->output(); - - // jpeg test is causing trouble - if($type === QRCode::OUTPUT_IMAGE_JPG){ - $this->markTestSkipped('jpeg test skipped'); - } - // skip png for PHP 7.2 https://travis-ci.org/codemasher/php-qrcode/jobs/292110988 - else if($type === QRCode::OUTPUT_IMAGE_PNG && PHP_VERSION_ID >= 70200){ - $this->markTestSkipped('png test skipped, PHP version: '.PHP_VERSION); - } - else{ - $this->assertEquals(file_get_contents(__DIR__.'/image/'.$expected), $output); - } - } - -} diff --git a/tests/Output/MarkupTest.php b/tests/Output/MarkupTest.php deleted file mode 100644 index 50aee4b94..000000000 --- a/tests/Output/MarkupTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - * @copyright 2016 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeTest\Output; - -use chillerlan\QRCode\Output\QRMarkup; -use chillerlan\QRCode\Output\QRMarkupOptions; -use chillerlan\QRCode\QRCode; - -/** - * Class MarkupTest - */ -class MarkupTest extends OutputTestAbstract{ - - protected $outputInterfaceClass = QRMarkup::class; - protected $outputOptionsClass = QRMarkupOptions::class; - - public function testOptions(){ - $this->assertEquals(QRCode::OUTPUT_MARKUP_SVG, $this->options->type); - } - - public function markupDataProvider(){ - return [ - [QRCode::OUTPUT_MARKUP_HTML, true, 'foobar', 'str1.html'], - [QRCode::OUTPUT_MARKUP_HTML, false, 'foobar', 'str2.html'], - [QRCode::OUTPUT_MARKUP_HTML, true, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str3.html'], - [QRCode::OUTPUT_MARKUP_HTML, false, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str4.html'], - [QRCode::OUTPUT_MARKUP_SVG , null, 'foobar', 'str1.svg'], - [QRCode::OUTPUT_MARKUP_SVG , null, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str2.svg'], - ]; - } - - /** - * @dataProvider markupDataProvider - */ - public function testMarkupOutput($type, $omitEndTag, $data, $expected){ - $this->options->type = $type; - $this->options->htmlOmitEndTag = $omitEndTag; - $this->options->cssClass = 'test'; - $this->assertEquals(file_get_contents(__DIR__.'/markup/'.$expected), (new QRCode($data, new $this->outputInterfaceClass($this->options)))->output()); - } - - public function markupTestDataProvider(){ - return [ - [QRCode::OUTPUT_MARKUP_SVG], - [QRCode::OUTPUT_MARKUP_HTML], - ]; - } - - /** - * @dataProvider markupTestDataProvider - */ - public function testSaveToFile(string $type){ - $this->options->type = $type; - $this->options->cssClass = 'foo'; - - $data = (new QRCode('foo', new $this->outputInterfaceClass($this->options)))->output(); - - $this->options->cachefile = __DIR__.'/markup/save_test.'.$type; - - $this->assertTrue((new QRCode('foo', new $this->outputInterfaceClass($this->options)))->output()); - - $this->assertContains($data, file_get_contents($this->options->cachefile)); - } - - /** - * @dataProvider markupTestDataProvider - * - * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException - * @expectedExceptionMessage Could not write to cache file - */ - public function testSaveToFileException(string $type){ - $this->options->type = $type; - $this->options->cachefile = __DIR__.'/foo/bar'; - - (new QRCode('foo', new $this->outputInterfaceClass($this->options)))->output(); - } - -} diff --git a/tests/Output/OutputTestAbstract.php b/tests/Output/OutputTestAbstract.php deleted file mode 100644 index 6e2c71c71..000000000 --- a/tests/Output/OutputTestAbstract.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @copyright 2016 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeTest\Output; - -use PHPUnit\Framework\TestCase; - -/** - * Class OutputTestAbstract - */ -abstract class OutputTestAbstract extends TestCase{ - - protected $outputInterfaceClass; - protected $outputOptionsClass; - - protected $options; - protected $outputInterface; - - protected function setUp(){ - $this->options = new $this->outputOptionsClass; - $this->outputInterface = new $this->outputInterfaceClass($this->options); - } - - public function testInstance(){ - $this->assertInstanceOf($this->outputInterfaceClass, $this->outputInterface); - $this->assertInstanceOf($this->outputOptionsClass, $this->options); - } - - /** - * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException - * @expectedExceptionMessage Invalid output type! - */ - public function testOutputTypeException(){ - $this->options->type = 'foo'; - new $this->outputInterfaceClass($this->options); - } - - /** - * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException - * @expectedExceptionMessage Invalid matrix! - */ - public function testSetMatrixException(){ - /** @var \chillerlan\QRCode\Output\QROutputInterface $outputInterface */ - $outputInterface = new $this->outputInterfaceClass; - $outputInterface->setMatrix([]); - } - -} diff --git a/tests/Output/StringTest.php b/tests/Output/StringTest.php deleted file mode 100644 index 83cc2c7ac..000000000 --- a/tests/Output/StringTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeTest\Output; - -use chillerlan\QRCode\Output\QRString; -use chillerlan\QRCode\Output\QRStringOptions; -use chillerlan\QRCode\QRCode; - -class StringTest extends OutputTestAbstract{ - - protected $outputInterfaceClass = QRString::class; - protected $outputOptionsClass = QRStringOptions::class; - - public function testOptions(){ - $this->assertEquals(QRCode::OUTPUT_STRING_JSON, $this->options->type); - } - - public function stringDataProvider(){ - return [ - [QRCode::OUTPUT_STRING_JSON, 'foobar', 'str1.json'], - [QRCode::OUTPUT_STRING_JSON, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str2.json'], - [QRCode::OUTPUT_STRING_TEXT, 'foobar', 'str1.txt'], - [QRCode::OUTPUT_STRING_TEXT, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str2.txt'], - // https://github.com/codemasher/php-qrcode/issues/4 - [QRCode::OUTPUT_STRING_TEXT, 'eyJjdCI6IjVPSExXNzZQZUg1NitEUUdtcFwvY2FBPT0iLCJpdiI6ImY0ZTI4MGMyYjc2NDExZmJjMmUzMjc5NzA0MTc3YmI4IiwicyI6ImZjZTZlMTY3YjNjMTQwMDUifQ%3D%3D', 'str3.txt'], - ]; - } - - /** - * @dataProvider stringDataProvider - */ - public function testStringOutput($type, $data, $expected){ - $this->options->type = $type; - $this->assertEquals(file_get_contents(__DIR__.'/string/'.$expected), (new QRCode($data, new $this->outputInterfaceClass($this->options)))->output()); - } - -} diff --git a/tests/Output/image/img1.gif.uri b/tests/Output/image/img1.gif.uri deleted file mode 100644 index 47a3478a4..000000000 --- a/tests/Output/image/img1.gif.uri +++ /dev/null @@ -1 +0,0 @@ -data:image/gif;base64,R0lGODlhcwBzAIAAAAQCBP///yH5BAEAAAEALAAAAABzAHMAAAL+jI+py+0Po5y02ouz3rz7D4biSJYXgKbqyrbt5L5ITNerY+c0rCf6z8IBhwBezkcECpM9yQ/JPDaizchzRrUtUZ0bVmpVdcVT8sZ7uIZTY3bZfTYb1BC0xq7AY/D0h/4klxe4F9i3VcTxl1ZYNTf4xfUWuQjnCGY5ucDXGKCIiciwWdnJ6Uk62meqyoha+kiZeaoFCar5+gkVm3orW9u7Q3soHEwLDOub67s7mmxs23rZO3zsLC1ZM/1ZvQo9e3xYndwcI3497lIeypmuTU6s3o39/uxNr+zKPP/r/l3MKtOPGj9c5vyh07dtoDWBBwPaOweQIENdCkXVW/jQYET+jPviWbwXjRvFkP+CaAQ58qJIZIDyOeTILsPKNrFizkzEy5SRmghz8trJ8iUhlxKLCiJK4WYcpB+zyfSZJWE8m1HDnaxKderVqOCqlsya0utKsVpffiTLc6JXsCjbog1qYs3GuHRDWK2Ld+ncvHyH7u0LGKjOwEfDsnUaE7E+p00XOxZaEF7ajGa/RqZcB+rkwm0PX+YMVLHkzj2ZaoYL+nNXIn7lVTYdDaKSliotj77bjjVt10bHKpRtyAk+w9dwdxzSWirx279PAl/nfGvZ3JuVR1kd1rps7G6TcH8+/Thq60y+S19OfXxFrsVLao/OHPbf3t7h2/Mt3+Ro8Bv+jftvrhZj9QXYXn74VefecPrdd9qB6nmkoIMCGrheeMHFh2B4/JH22kXm7fdYeo11yJsfANJHYUMoZnciZ0oZtaFnag1GIGEhJtXihYDRmJmKLrbIF48m+hijjTcK1iCQeQn5IYNEnnddgegN2V+ObyWpoZTdpShWkzBpuZ2WWIF5pH3icTcmhhz2WOWTkFkgoXCxmZnYbl6iiWWGm4VGplwlhgmiXg/O96Ob6YlWQZx+/mcoR8uw6OOIMNJZWm0QRmrbpDViJp6knWbqaJ6DbtkmpKWu+eWmqc0I6qOoKsqpqxNOCaimsSo4K6q1pnorSfnJ6CStn8k6LKi7CnmMVoV6vipqnYGK2KqypP6pWqUlJmvhgGyOWiSJnpYn57LdrrhgqLr5met7qmLrYbVvJvqTmPGiGy5qfMIJHb5IOQvvvvL6S2/Ad8wbbLmt3TswwAUz6e7C7HmbYJaqbgguxJcKOzGUFZPLrq6UspoFWx3ze+a/VIgcMcaHGslyyy6/DHPMMs9Mc80WFAAAOw== \ No newline at end of file diff --git a/tests/Output/image/img1.jpg.uri b/tests/Output/image/img1.jpg.uri deleted file mode 100644 index b56948582..000000000 --- a/tests/Output/image/img1.jpg.uri +++ /dev/null @@ -1 +0,0 @@ -data:image/jpg;base64,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 \ No newline at end of file diff --git a/tests/Output/image/img1.png.uri b/tests/Output/image/img1.png.uri deleted file mode 100644 index ae3a74bac..000000000 --- a/tests/Output/image/img1.png.uri +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHMAAABzCAIAAAAkIaqxAAAABnRSTlMA/wD/AP83WBt9AAACGUlEQVR4nO3cwU4DMQwAUYL4/19e7j54ZTmTuNW8G6K0MIqw0mZ3Pc/zI8Dv7V/ga1mWYlmKZSl/4eu11pkXLk3O0m8VnvnWX+SapViWYlmKZSlxggUbd2j5JOnMqJJjf5FrlmJZimUplqW8TLCAmzP5xil/qtKD85/NlZ7ZNUuxLMWyFMtSahPslnzOzPwozzVLsSzFshTLUqZMsNKMCg/u7ME4rlmKZSmWpViWUptg3HDoHL8Y8rFY4JqlWJZiWYplKS8T7NhBvnwf1flu/kIc1yzFshTLUixLWUPecws+8YOvwDVLsSzFshTLUl6uB9u4F+p8WnXsLOLGF3LNUixLsSzFspS4B9v4Jltnp7TxEuZjbzl6RfMhlqVYlmJZSuskB3eR8sa5WvrZ0vTLuWYplqVYlmJZSus0fekf/K2rkm+dxHfNUixLsSzFspTaXaXy//elWdF5odJ3bx0Kcc1SLEuxLMWylClnEbm90MadYc7PwQ6xLMWyFMtSwJMcuc7o2HjKsfNC+YNdsxTLUixLsSxlyr3pO7h9VIdrlmJZimUplqVMuTf9xtOG+ese23O6ZimWpViWYlnKlDv7Drl0ujMMA9csxbIUy1IsS5kywbi90MZ9Y2lUumYplqVYlmJZytB705ccOy/vXaVGsCzFshTLUj7y3vRB55pl7i1H1yzFshTLUixLmXI92PdxzVIsS7EsxbKUf5cMC/onT8WdAAAAAElFTkSuQmCC \ No newline at end of file diff --git a/tests/Output/image/img2.gif.uri b/tests/Output/image/img2.gif.uri deleted file mode 100644 index a33a1d16f..000000000 --- a/tests/Output/image/img2.gif.uri +++ /dev/null @@ -1 +0,0 @@ -data:image/gif;base64,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 \ No newline at end of file diff --git a/tests/Output/image/img2.jpg.uri b/tests/Output/image/img2.jpg.uri deleted file mode 100644 index 5aa4e2332..000000000 --- a/tests/Output/image/img2.jpg.uri +++ /dev/null @@ -1 +0,0 @@ -data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2OTApLCBxdWFsaXR5ID0gODUK/9sAQwAFAwQEBAMFBAQEBQUFBgcMCAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQiHiQcHh8e/9sAQwEFBQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e/8AAEQgAwwDDAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+kvjX8UNA+E3hW28R+I7PU7q0ub5LJEsI0eQOySOCQ7oNuI275yRxXj/APw2r8LP+gB4z/8AAO2/+SKP+Cjv/JENG/7GSD/0mua5/wDZq+B/wc8S/s96J408aeHYZLuSK8mv7+bVLm3jVIrmZd7bZVRVVEGTgDAye5oA6D/htX4Wf9ADxn/4B23/AMkUf8Nq/Cz/AKAHjP8A8A7b/wCSKP8AhWn7G3/QS8Gf+FnJ/wDJNXdE+D37JeuapDpeiDwzqd/Pu8q1s/Fc00sm1Sx2otwScKCTgdATQBS/4bV+Fn/QA8Z/+Adt/wDJFfTNfm1+2t4C8J/Dv4p6Zong7Sf7MsJ9EiupIvtEs26Vp51LZkZj91FGM44+tfpLQB4Z8Uv2oPAPw78d6j4O1vSPE1xf6f5XmyWdtA0TeZEko2lplP3XGcgc5+tcz/w2r8LP+gB4z/8AAO2/+SK+Zv22v+TnfF3/AG5f+kUFdn+wx8LfAnxK/wCEx/4TXQv7V/s77D9k/wBLnh8vzPtG/wD1TrnOxOucY470AfRnwt/ag8A/ETx3p3g7RNI8TW9/qHm+VJeW0CxL5cTyncVmY/dQ4wDzj61qfGv9oXwX8JvFVt4c8R6Z4guru5sUvUewgheMIzyIAS8qHdmNu2MEc15l8RfB3wl8AaNqeqfA0aYPirpcoh0yz07Vn1G/RzIIrlBZvJJvZYWn3AxkqAzcFcjM+HGmeDfH+hzaz+1RJZWfjWG5a2sU1+9OiXB04KrRlYEaEMnmvcYk2nJDDJ24AB03/Davws/6AHjP/wAA7b/5Ir2Dw18UNA1/4NS/FSzs9Tj0WKxu71oJY0FzstjIHAUOV3Hymx82ORkjt+af/Cp/in/0TTxn/wCCK5/+Ir7f/ZqvfCNl+z3onwt8d6rpml61dxXmn33h/Ub1bS/IubmbbGYWZZVaRJVK4AJDqR1BoA7r4F/Gvwr8Yf7Y/wCEZ0/WrT+yPI+0f2jDEm7zfM27dkj5x5TZzjqOvb0yvjP9pr/jGv8A4R//AIUp/wAUr/wkf2n+1f8Al9+0fZ/K8n/j58zZt8+X7uM7uc4GPRvEfxevdR/ZrsLnwP4y0zVfifcaRp0osdOe3u797gmE3QFoobLBPOLKE+UBjgbeAD6GrhvjX8UNA+E3hW28R+I7PU7q0ub5LJEsI0eQOySOCQ7oNuI275yRxXM/sm658S9f+HWoXnxUg1OHWk1eSKBb/TBZSfZxDCVIQImV3mT5sdcjPHHwN8R/jN8SviJocOieMfEn9p2EFyt1HF9ht4dsqqyhsxxqfuuwxnHP0oA/R/w18UNA1/4NS/FSzs9Tj0WKxu71oJY0FzstjIHAUOV3Hymx82ORkjt4/wD8Nq/Cz/oAeM//AADtv/kip/gZaXV/+wDPY2NtNdXdz4f1uGCCGMvJK7S3YVFUcsxJAAHJJr4T8T+E/FXhf7P/AMJN4a1rRPtO77P/AGjYy2/m7cbtu9RuxuXOOmR60AfrX4T1u18S+FdJ8R2Mc0dpqtjDewJMAJFSVA6hgCQGwwzgkZ7mvM/jX+0L4L+E3iq28OeI9M8QXV3c2KXqPYQQvGEZ5EAJeVDuzG3bGCOa+bP2PvjN8StY+LXg74f6j4k8/wANR20tqtl9ht1xFBZyGJfMWMP8pjTndk45zk1S/wCCjv8AyW/Rv+xbg/8ASm5oA9m/4bV+Fn/QA8Z/+Adt/wDJFH/Davws/wCgB4z/APAO2/8Akiug8Sfs+/s1eGrFL7xHoWmaNaSSiFJ7/wAQ3VvGzkEhAzzgFsKxx1wD6Vz/APwrT9jb/oJeDP8Aws5P/kmgA/4bV+Fn/QA8Z/8AgHbf/JFdN8Lf2oPAPxE8d6d4O0TSPE1vf6h5vlSXltAsS+XE8p3FZmP3UOMA84+tU/DHwI/Zg8UfaP8AhGdL0XW/s237R/Z3iS4uPK3Z27tlwdudrYz1wfSvnL9n7S7DQ/28BomlwfZ7DT9b1m1tYt7N5cUcN0qLliScKAMkk+tAH6GUUUUAfM3/AAUd/wCSIaN/2MkH/pNc0fBv/lHnff8AYt67/wCjLuj/AIKO/wDJENG/7GSD/wBJrmrv7P2l3+ufsHjRNLg+0X+oaJrNraxb1XzJZJrpUXLEAZYgZJA9aAPzzr2b9iX/AJOd8I/9vv8A6RT0f8MufHb/AKEb/wAq1l/8er039lz4CfFjwX8dvDvibxN4U+waTZ/avtFx/aFrJs32sqL8qSFjlmUcA9fSgDF/4KO/8lv0b/sW4P8A0pua+/6+AP8Ago7/AMlv0b/sW4P/AEpua+2fiP498J/DvQ4db8Y6t/ZlhPcraxy/Z5Zt0rKzBcRqx+6jHOMcfSgD5z/ao/Zv/wCEi1fxr8W/+Ez+y+VprX39m/2Zvz9mtFGzzfNH3vK67ON3Q45+f/2Zfjp/wpb/AISD/ilv7d/tn7N/zEPs3k+T5v8A0zfdnzfbG3vnj3PV9O+K3xP+OVl418FahrWr/B/U9Ssg6HV/Jsrizj8qK8R7OWRWKFknVkaP5+eGDc3f2wfgDqPiL/hFv+FSfD3RYfs/2v8AtL+zktLDO7yfK3ZKb/uyY6456Z5AMX/hBf8AhXf/ABlt/an9p/a/+J7/AMI19n8nb/afyeV9q3Nny/ted3lfNs6Lu4P+EF/4bB/4uZ/an/CE/wBmf8SL7B9n/tHzPL/f+b5m6LGftO3btONmcnOB6B4f+MnwS0P4aaP8NfiJrFk9/o2m2ul6zpV5o893FHdWyIkkbYiaOTZLGcMpZcqCCeDWn4b/AGgv2avDVi9j4c13TNGtJJTM8Fh4eureNnIALlUgALYVRnrgD0oA639o74r/APCnvA9n4m/sH+2/tOpJY/Z/tn2fbuilffu2PnHlYxjv145+APGXxX/4SL9oaD4t/wBg/ZfK1Kwvv7N+2b8/ZlhGzzdg+95XXZxu6HHP2x+2t4C8WfET4WaZong7Sf7Tv4NbiupIvtEUO2JYJ1LZkZR951GM55+tH7O/wU8PaP8AB3QtO+IHw48My+JYftH21rzT7W6lObiVo90oDBv3ZTHzHAwOMYoA8m/5PR/6kT/hDv8AuJfa/tn/AH58vZ9l/wBrO/tjns/gl+yl/wAK1+J+keNf+E9/tX+zvO/0T+yPJ8zzIJIvv+c2Mb89DnGPeuM/bn/4tf8A8Id/wrT/AIon+0/t32//AIR7/iXfa/L+z+X5vkbd+3zJNu7ON7Yxk10Fx8XrLx/+z/pfw8+HnjLU734q3ekWEUccT3FtcvcQiKS6JupAibtkcxLGT5uQCSwBAOt/aO/aQ/4U944s/DP/AAhn9t/adNS++0f2n9n27pZU2bfKfOPKznPfpxz8Z/s4/Cj/AIXD44vPDP8Ab39ifZtNe++0fY/tG7bLEmzbvTGfNznPbpzxmfGvQ/iXoHiq2s/ipPqc2tPYpLA1/qYvZPs5eQKA4d8LvEny565OOefp/wDYp+DPxK+HfxT1PW/GPhv+zLCfRJbWOX7dbzbpWngYLiORj91GOcY4+lAHufg3Qv8AhRv7PM+n/av+Eg/4RfTb++3+X9l+04aa42Yy+z723PzdM47V8M/tNfHT/hdP/CP/APFLf2F/Y32n/mIfafO87yv+mabceV753dsc/Znxx+KXgSTT/FPwkTXc+NdU02XS7PTfsk/726urfFvH5uzyhuMsfzFwo3ckYOPgD4m/C3x38Nf7P/4TXQv7K/tHzfsn+lwTeZ5ezf8A6p2xjenXGc8d6AOz/Yl/5Od8I/8Ab7/6RT12f/BR3/kt+jf9i3B/6U3NcZ+xL/yc74R/7ff/AEinrs/+Cjv/ACW/Rv8AsW4P/Sm5oA9m/wCCjv8AyRDRv+xkg/8ASa5r4Ar9Jf21vAXiz4ifCzTNE8HaT/ad/BrcV1JF9oih2xLBOpbMjKPvOoxnPP1r5A/4Zc+O3/Qjf+Vay/8Aj1AHs3/BMv8A5qD/ANw3/wBuq4z4N/8AKQy+/wCxk13/ANF3dezfsMfC3x38Nf8AhMf+E10L+yv7R+w/ZP8AS4JvM8v7Rv8A9U7YxvTrjOeO9eM/Bv8A5SGX3/Yya7/6Lu6APv8AooooA+Zv+Cjv/JENG/7GSD/0mua+c/hb+1B4++HfgTTvB2iaR4ZuLDT/ADfKkvLadpW8yV5TuKzKPvOcYA4x9a+7PjX8UNA+E3hW28R+I7PU7q0ub5LJEsI0eQOySOCQ7oNuI275yRxWn8LfGulfETwJp3jHRLe9t7DUPN8qO8RVlXy5XiO4KzD7yHGCeMfSgD4m/wCG1fin/wBADwZ/4B3P/wAkUf8ADavxT/6AHgz/AMA7n/5Ir6z+Onxr8K/B7+x/+Em0/Wrv+1/P+z/2dDE+3yvL3bt8iYz5q4xnoenfT8S/FDQNA+DUXxUvLPU5NFlsbS9WCKNDc7LkxhAVLhdw81c/Njg4J7gH5tfGv4oa/wDFnxVbeI/Ednplrd21ilkiWEbpGUV5HBId3O7Mjd8YA4r7F/4KO/8AJENG/wCxkg/9Jrmj/htX4Wf9ADxn/wCAdt/8kV7B8a/ihoHwm8K23iPxHZ6ndWlzfJZIlhGjyB2SRwSHdBtxG3fOSOKAPMv2b9buvDX7Dtv4jsY4ZLvStI1e9gSYExs8U906hgCCVyozgg47ip/2PvjX4q+MP/CU/wDCTafotp/ZH2T7P/Z0MqbvN87du3yPnHlLjGOp69vlnUvGulfET9tHQPGOiW97b2GoeJNG8qO8RVlXyzbxHcFZh95DjBPGPpX2z8dPjX4V+D39j/8ACTafrV3/AGv5/wBn/s6GJ9vleXu3b5ExnzVxjPQ9O4B8Tab4K0r4ifto6/4O1u4vbew1DxJrPmyWbqsq+WbiUbSysPvIM5B4z9azP2svhfoHwm+Iun+HPDl5qd1aXOkR3rvfyI8gdppkIBREG3Ea9s5J5r6G+CnwU8VTfH6x+O66hoo8Na1c3mt21qZpftqQX0MrRK6eXsDgTpuAcgYOC3GfMv8Ago7/AMlv0b/sW4P/AEpuaAPqD9rL4oa/8Jvh1p/iPw5Z6ZdXdzq8dk6X8bvGEaGZyQEdDuzGvfGCeKPAPxQ1/X/2V7n4qXlnpketRaRqV6sEUbi232zThAVLltp8pc/Nnk4I7cL/AMNq/Cz/AKAHjP8A8A7b/wCSK+QP2iPGulfET4xa74x0S3vbew1D7P5Ud4irKvl28UR3BWYfeQ4wTxj6UAfRnwy/4zB/tD/hZf8AxKP+ES8r7B/wj37jzPte/wAzzfP83OPs0e3btxls5yMec/s36Ja+Gv24rfw5YyTSWmlavq9lA8xBkZIoLpFLEAAthRnAAz2FcN8C/gp4q+MP9sf8IzqGi2n9keR9o/tGaVN3m+Zt27I3zjymznHUde3Wfso6JdeGv2x9I8OX0kMl3pV9qdlO8JJjZ4rW5RipIBK5U4yAcdhQB0H/AAUd/wCS36N/2LcH/pTc19QftZfFDX/hN8OtP8R+HLPTLq7udXjsnS/jd4wjQzOSAjod2Y174wTxXn/7WX7PXjT4s/EXT/EfhzU/D9raW2kR2TpfzzJIXWaZyQEicbcSL3zkHipv+G1fhZ/0APGf/gHbf/JFAHM6N4K0rx/8PJP2qNYuL2Dxra20+tpY2jqummfTi6QKY2VpdjC1j3jzcnLYK5GKXwy/4zB/tD/hZf8AxKP+ES8r7B/wj37jzPte/wAzzfP83OPs0e3btxls5yMdn/w2r8LP+gB4z/8AAO2/+SKP+G1fhZ/0APGf/gHbf/JFAHg37N+iWvhr9uK38OWMk0lppWr6vZQPMQZGSKC6RSxAALYUZwAM9hWn/wAFHf8Akt+jf9i3B/6U3NfY3iX4oaBoHwai+Kl5Z6nJostjaXqwRRobnZcmMICpcLuHmrn5scHBPc+CnxQ0D4s+FbnxH4cs9TtbS2vnsnS/jRJC6pG5ICO424kXvnIPFAHxz/w2r8U/+gB4M/8AAO5/+SKP+G1fin/0APBn/gHc/wDyRX1B8FP2hfBfxZ8VXPhzw5pniC1u7axe9d7+CFIyivGhAKSud2ZF7YwDzXr9AHwB/wANq/FP/oAeDP8AwDuf/kiuf/ZR1u68S/tj6R4jvo4Y7vVb7U72dIQRGry2ty7BQSSFyxxkk47mv0fooAKKKKAOG+Neh/DTX/CttZ/FSfTIdFS+SWBr/UzZR/aAkgUBw6ZbYZPlz0ycccafwt0zwbo/gTTtO+H8llL4ah837E1nem6iOZXaTbKWYt+8L5+Y4ORxjFeGf8FHf+SIaN/2MkH/AKTXNbX7MGu/8Iv+xVp/ib7L9r/sjTdVvvs/mbPN8q4uX2bsHbnbjODjPQ0Adn8dPDXwa8Rf2P8A8LbudFh+z+f/AGb/AGjrLWGd3l+btxKm/wC7Hnrjjpnn4g+I/wAR/iL4n1bXPg94T1ObXPB8d9LYaNpWm2EVy0llaylrcRyIhlkVUhRt+4khcknJJ9f/AOT0f+pE/wCEO/7iX2v7Z/358vZ9l/2s7+2OfAPBuu/8KN/aGn1D7L/wkH/CL6lf2OzzPsv2nCzW+/OH2fe3Y+bpjPegD2b9nH4X/CGHwPeL8d9OstA8SnUnNrba/qs2lXD2flRbHWJpIyyGTzgH2nJVhn5cD6s+Neh/DTX/AArbWfxUn0yHRUvklga/1M2Uf2gJIFAcOmW2GT5c9MnHHHzZ/wAIL/w2D/xcz+1P+EJ/sz/iRfYPs/8AaPmeX+/83zN0WM/adu3acbM5OcD6A/aO+FH/AAuHwPZ+Gf7e/sT7NqSX32j7H9o3bYpU2bd6Yz5uc57dOeADx/xZ4S/Ze8LeFdW8T+CNX8JQ+KtIsZr/AESSHxS1xIl7ChkgKxNOyyMJFTCFWDHgg5xXP/sy/wDGSn/CQf8AC6/+Kq/4Rz7N/ZX/AC5fZ/tHm+d/x7eXv3eRF97ONvGMnPzN8bfAv/Ctfifq/gr+1P7V/s7yf9L+z+T5nmQRy/c3NjG/HU5xn2rs/wBmX46f8KW/4SD/AIpb+3f7Z+zf8xD7N5Pk+b/0zfdnzfbG3vngA+xvi94207wx8I7/AMJ/B7xHpkvjDR4rfT9K0bTriK/v4xDLHHJGLdt7uyQrJu3KSArE9CR5Z8ONM8G+P9Dm1n9qiSys/GsNy1tYpr96dEuDpwVWjKwI0IZPNe4xJtOSGGTtwOm+CXwL874n6R+0D/wlO3+3PO13+xf7Pz5H2+CRvK8/zPm2efjd5Y3bei542v2jv2b/APhcPjiz8Tf8Jn/Yn2bTUsfs/wDZn2jdtllffu81MZ83GMduvPAB+fPhvw9r/iW+ex8OaHqes3ccRmeCwtHuJFQEAuVQEhcsoz0yR60Xvh7X7LxEvhy80PU7bWmljhXTpbR0uS8mCiCIjdubcuBjJ3DHWvrn/hBf+GPv+Lmf2p/wm39p/wDEi+wfZ/7O8vzP3/m+Zulzj7Nt27RnfnIxg7XgT4Uf8Ls8caD+0h/b39g/bNSt77+wvsf2nZ9hlWHZ9o3pnf8AZt2fL+XfjDYyQD5m8MeJfjL8DftH9n22teD/AO29u/8AtHRlX7T5OcbftER+75pzt/vjPavqXV/AFtpfwNsvjZ4C8PXs3xavtNstUGoWazXUs11eeV9rkW1JaI7kmnJUR7VBJULtGOZ/4Kaf80+/7iX/ALa1jeBP2yv+EX8D6D4Z/wCFcfa/7I023sftH9t7PN8qJU37fIO3O3OMnGepoA9m/Zx+Kerw+B7xfjv4ostA8SnUnNrba+INKuHs/Ki2OsTCMshk84B9pyVYZ+XA/Oavsz/hBf8AhsH/AIuZ/an/AAhP9mf8SL7B9n/tHzPL/f8Am+Zuixn7Tt27TjZnJzgfGdAHTfCfS7DXPin4S0TVIPtFhqGt2VrdRb2XzIpJ0V1ypBGVJGQQfSv0M/4Zc+BP/Qjf+Va9/wDj1fAHwJ/5Lf4D/wCxk07/ANKY6+//ANpr46f8KW/4R/8A4pb+3f7Z+0/8xD7N5Pk+V/0zfdnzfbG3vngA+M/jj8UvHceoeKfhImu48FaXqUul2em/ZIP3Vra3GLePzdnmnaIo/mLljt5Jycw/BTxf8ftA8K3Nn8K7TxBNor3zyztYeH1vY/tBSMMC5hfDbBH8uemDjnnznx3rv/CUeONe8TfZfsn9r6lcX32fzN/lebKz7N2BuxuxnAzjoK9m/Zx/aQ/4U94HvPDP/CGf239p1J777R/af2fbuiiTZt8p848rOc9+nHIB9i+G/h18FvgtfP4psbfTPCct1EdPN5f6xKscgYiTyx58pXcfKB45wp7Zr5m+PP7Q3xOtvjhqvh/4YeNIb/RXltYdLi02ytL1ZneCLckb+W7SMZWYYBJzx2xX1B+0d8KP+Fw+B7Pwz/b39ifZtSS++0fY/tG7bFKmzbvTGfNznPbpzx4z4E/Y1/4RfxxoPib/AIWP9r/sjUre++z/ANibPN8qVX2bvPO3O3GcHGehoA2f2Zfil47h/wCEg/4aB13/AIR3P2b+xf8AhIbSDSPP/wBb5/lbkj83b+53ddu5em7nk/hL8cfH/if9rt/Br+LYdR8Hy6vqkVtDDa2xjkt4o7hoCsqJuZfkQhgxyByTnn1/9pr4F/8AC6f+Ef8A+Kp/sL+xvtP/ADD/ALT53neV/wBNE248r3zu7Y54z4Jfspf8K1+J+keNf+E9/tX+zvO/0T+yPJ8zzIJIvv8AnNjG/PQ5xj3oA+maKKKAMzxJ4e0DxLYpY+I9D0zWbSOUTJBf2iXEauAQHCuCA2GYZ64J9a5L4t6TpWh/s+eNtL0TTLLTLCDw3qXlWtnAsMUe63lY7UUADLEk4HUk1gftZfFDX/hN8OtP8R+HLPTLq7udXjsnS/jd4wjQzOSAjod2Y174wTxVz4R63dfGn9nOG+8Uxw2cviWxvrK8GmgxrGhlmt8x+YXw2wA87hntjigD5T/YY+KXgT4a/wDCY/8ACa67/ZX9o/Yfsn+iTzeZ5f2jf/qkbGN6dcZzx3r6G8L+Lf2XviJ41j0jSNI8Jaz4h1WWWYCbwswkuHCtLI7SSwAFsKzEsck+pNc//wAMVfCz/oP+M/8AwMtv/keum+Fv7L/gH4d+O9O8Y6Jq/ia4v9P83yo7y5gaJvMieI7gsKn7rnGCOcfSgDgP2jvhf8XofHFmvwI0690Dw0dNQ3VtoGqw6VbveebLvdolkjDOY/JBfachVGflwPnL4ca38f8A4ia5Nong7xr4z1O/gtmupIv+Elkh2xKyqWzJKo+86jGc8/WvqD9rL9oXxp8JviLp/hzw5pnh+6tLnSI713v4JnkDtNMhAKSoNuI17ZyTzXyB8FPihr/wm8VXPiPw5Z6ZdXdzYvZOl/G7xhGeNyQEdDuzGvfGCeKAO51v9nL9ovXNUm1TW/DF7qd/Pt826vNdtJpZNqhRudpyThQAMnoAKpf8MufHb/oRv/KtZf8Ax6vtPwD8UNf1/wDZXufipeWemR61FpGpXqwRRuLbfbNOEBUuW2nylz82eTgjt8v/APDavxT/AOgB4M/8A7n/AOSKAPpnxh4Y+IMf7JWn+E/CaXtn41tdE0q1RLO/S3liliMAmVZg4UYVZASGwRkDOeeG+CnxKb4N+Fbnwx+0N4q1Oy8VXV89/aR38s+pyGyZI40Ilh81VXzIpvkLAggnHzAn3/4a63deJfh14a8R30cMd3qukWt7OkIIjV5YVdgoJJC5Y4yScdzXAfGv9nrwX8WfFVt4j8R6n4gtbu2sUskSwnhSMoryOCQ8TndmRu+MAcUAfNn7FOrar8SvinqehfEbU73xlpMGiS3cNjr87X9vHOs8CLKscxZQ4V3UMBkB2GcE19y6JpOlaHpcOl6JpllplhBu8q1s4Fhij3MWO1FAAyxJOB1JNfJvxH8FaV+yZocPxG+HNxe6rq2o3K6JNDr7rNbrBIrTMyrCsTB91ugBLEYLcZwR7/8As7+NdV+Inwd0LxjrdvZW9/qH2jzY7NGWJfLuJYhtDMx+6gzknnP0oA6fxP4T8K+KPs//AAk3hrRdb+zbvs/9o2MVx5W7G7bvU7c7VzjrgelfJn7U3iT9neP4c+LPCfhPS/DNn41tbmO1RLPw4beWKWK6jEyrMIQowqyAkNgjIGc8/ZlfPPiz9kT4beJfFWreI77W/Fsd3qt9NezpDdW4jV5XLsFBgJC5Y4yScdzQB8m/BTwh8ftf8K3N58K7vxBDoqXzxTrYeIFso/tASMsShmTLbDH82OmBnjj0D4KfDVfg34qufE/7Q3hXTLLwrdWL2FpJfxQanGb1njkQCKHzWVvLim+cqAACM/MAd/4j+NdV/ZM1yH4c/Dm3stV0nUbZdbmm19GmuFnkZoWVWhaJQm23QgFSclucYAPhx411X9rPXJvhz8Rrey0rSdOtm1uGbQEaG4aeNlhVWaZpVKbbhyQFByF5xkEAxtX8AXOqfHKy+NngLw9ZQ/CWx1Ky1QahZrDaxQ2tn5X2uRbUlZRteGclRHuYglQ24Z+pvDHiX4NfHL7R/Z9tovjD+xNu/wDtHRmb7N52cbftEQ+95Rzt/uDPatPw18L9A0D4NS/CuzvNTk0WWxu7Jp5ZENzsuTIXIYIF3DzWx8uOBkHvmfAv4KeFfg9/bH/CM6hrV3/a/kfaP7Rmifb5XmbduyNMZ81s5z0HTuAfEHh648BeGP2w9YfxlY6ZD4PsfEGrwzW0un+fbRoPtCRIIFRsqH8sABcDAPGOKX7WWufDTX/iLp958K4NMh0VNIjinWw0w2Uf2gTTFiUKJlthj+bHTAzxxyXx2/5Lf48/7GTUf/SmSuMoA/ZivhP46/EzxL4W/bPe3uPG3iDTvCtjq+ky3lnFfz/Zkt/JtnmBhQ4ZSC5KhTuyeDmvo39rL4oa/wDCb4daf4j8OWemXV3c6vHZOl/G7xhGhmckBHQ7sxr3xgnivDNT+F+gfGT4Ja1+0L4nvNTs/FV3pF9eyWmnSJHYh7NZIYgEdHk2lbdC37zJJbBHAABmftg/H7TvEX/CLf8ACpPiFrUP2f7X/aX9nPd2Gd3k+VuyE3/dkx1xz0zzzH7IHxC8fa5+0T4X0vW/HHibU7Cf7X5trearPNFJttJmG5GYg4YAjI6gGqf7H3wU8K/GH/hKf+Em1DWrT+yPsn2f+zpok3eb527dvjfOPKXGMdT17Tfs36Ja+Gv24rfw5YyTSWmlavq9lA8xBkZIoLpFLEAAthRnAAz2FAH6JUUUUAfM3/BR3/kiGjf9jJB/6TXNaf7N+iXXiX9h238OWMkMd3qukavZQPMSI1eWe6RSxAJC5YZwCcdjXsHxH8BeE/iJocOieMdJ/tOwguVuo4vtEsO2VVZQ2Y2U/ddhjOOfpXzNq+o/Fb4YfHKy8FeCtP1rSPg/pmpWRdzpHnWVvZyeVLeO95LGzBAzzsztJ8nPKheADz//AIYq+Kf/AEH/AAZ/4GXP/wAj17z+0hol14a/YduPDl9JDJd6VpGkWU7wkmNnintUYqSASuVOMgHHYVyf7YPx+1Hw7/wi3/CpPiFos32j7X/aX9nPaX+Nvk+VuyH2fekx0zz1xxp/Frxtp3xG/ZETQtM8R6Z4k8c6rpGlyzaVp1xFNfzXAkt5bgC2i+bcu2RmVV+UK3AAOADyb9k39oXwX8Jvh1qHhzxHpniC6u7nV5L1HsIIXjCNDCgBLyod2Y27YwRzX0z8FP2hfBfxZ8VXPhzw5pniC1u7axe9d7+CFIyivGhAKSud2ZF7YwDzXzz+zj8L/hDD4HvF+O+nWWgeJTqTm1ttf1WbSrh7PyotjrE0kZZDJ5wD7Tkqwz8uB5L8ONE+P/w71ybW/B3grxnpl/PbNayS/wDCNSTbomZWK4kiYfeRTnGePrQB9G/Gv4KeKofj9ffHdtQ0U+GtFubPW7m1E0v214LGGJpVRPL2FyIH2guAcjJXnGL8Tf8AjMH+z/8AhWn/ABKP+ES837f/AMJD+48z7Xs8vyvI83OPs0m7dtxlcZycczoXjj9pfWtcsNG+I1l4mi8FX9zHbeInvfDKWluunOwW5Mk4gUxIIi+6QMu0ZORjNfU3wL8NfBrw7/bH/CpLnRZvtHkf2l/Z2stf42+Z5W7Mr7PvSY6Z5644APib4pfsv+Pvh34E1Hxjrer+Gbiw0/yvNjs7mdpW8yVIhtDQqPvOM5I4z9KzPgp+z140+LPhW58R+HNT8P2tpbXz2TpfzzJIXVI3JASJxtxIvfOQeK+v/wBrLxDoHin4L+KvBHhjXNM1zxVLLBDHomnXaXN+7xXcTSoIEJkLIqOzDblQjE4wa+M/DfxF+NPwWsX8LWNxqfhOK6lOoGzv9HiWSQsBH5g8+IttPlAccZU980Aem/tZftC+C/iz8OtP8OeHNM8QWt3bavHeu9/BCkZRYZkIBSVzuzIvbGAea+kv2Jf+TYvCP/b7/wCls9fMH7JvwKuNf+IuoWfxU+HfiCHRU0iSWBr+1u7KP7QJoQoDjZlthk+XPTJxxx03iP4keIvhH+0pYfCfwtr8Ph/4c6dq+nRNYzRQvHBb3AhmuS1xMrSBS00rFi/yg8EADABp/wDBTT/mn3/cS/8AbWvln4W+CtV+InjvTvB2iXFlb3+oeb5Ul47LEvlxPKdxVWP3UOMA84+tfRv/AAUO8WeFfFH/AAg3/CM+JdF1v7N/aH2j+zr6K48rd9m27tjHbna2M9cH0r1P9mTwh8AbS08Ea9oN34fbx+2kQyyRxeIGluftD2n+kA2/nEBsNLldny88DHAB4l/wxV8U/wDoP+DP/Ay5/wDkevsb41/FDQPhN4VtvEfiOz1O6tLm+SyRLCNHkDskjgkO6DbiNu+ckcV4z+1l4v8Aj9oHxF0+z+Fdp4gm0V9Ijlnaw8Prex/aDNMGBcwvhtgj+XPTBxzz7N8a9D+Gmv8AhW2s/ipPpkOipfJLA1/qZso/tASQKA4dMtsMny56ZOOOAD5T8bfBTxV+0T4nu/jH4K1DRdP0DxBs+yW+rzSxXafZ0W2fesUciDLwuRhz8pGcHIGL/wAMVfFP/oP+DP8AwMuf/keqXxS+NHib4Z+O9R8EfBnxfZWvgTTfK/sqKzjtr+JfMiSWbbPIsjv++klJy5wSQMAAD3P9hj4peO/iV/wmP/Ca67/av9nfYfsn+iQQ+X5n2jf/AKpFznYnXOMcd6APU7jW7X4Lfs/6XfeKY5ryLw1pFhZXg00CRpHAit8x+YUyu8g87Tjtnivg39rL4oaB8WfiLp/iPw5Z6na2ltpEdk6X8aJIXWaZyQEdxtxIvfOQeK9Tn+Ker+MP2jtc+FvxS8UWTfDVtb1G1u7S8EFnEsVu0z26tcKEkXEkUODvBYgA5yQeM/aO+FmkTeOLNvgR4Xvdf8NDTUF1c6AZ9Vt0vPNl3o0qmQK4j8klNwwGU4+bJANn/hir4p/9B/wZ/wCBlz/8j19WfCPRLr4Lfs5w2PimSG8l8NWN9e3h00mRZEEs1xiPzAmW2EDnaM98c18p/wDCy/2yf+gb4z/8IyP/AORqpeH/AIyfG3XPiXo/w1+ImsXqWGs6la6XrOlXmjwWkslrcuiSRtiJZI98UhwylWwwII4NAFP9sH41+FfjD/wi3/CM6frVp/ZH2v7R/aMMSbvN8nbt2SPnHlNnOOo69sX9iX/k53wj/wBvv/pFPXZ/tz/C3wJ8Nf8AhDv+EK0L+yv7R+3fa/8AS55vM8v7Ps/1rtjG9+mM557Vxn7Ev/JzvhH/ALff/SKegD9MqKKKAPM/2jviv/wp7wPZ+Jv7B/tv7TqSWP2f7Z9n27opX37tj5x5WMY79eOTwbrv/C8v2eZ9Q+y/8I//AMJRpt/Y7PM+1fZstNb784Tf93dj5euM967nxJ4e0DxLYpY+I9D0zWbSOUTJBf2iXEauAQHCuCA2GYZ64J9a+ZvEfw6+NOnftKWFz4Ht9T0r4YW+r6dKLHTtYitLFLcCE3QFosq4Uv5xZQnzEscHdyAc/wD8MMf9VR/8oH/3RXmf7MGhf8Iv+2rp/hn7V9r/ALI1LVbH7R5ezzfKt7lN+3J2525xk4z1NfcvxN+KXgT4a/2f/wAJrrv9lf2j5v2T/RJ5vM8vZv8A9UjYxvTrjOeO9fBvw08e+E9H/bJu/iBqOreR4ak1vVrpb37PK2Yp0uBE3lqpf5jInG3IzzjBoA+pv2jv2b/+Fw+OLPxN/wAJn/Yn2bTUsfs/9mfaN22WV9+7zUxnzcYx2688dn+0d8V/+FPeB7PxN/YP9t/adSSx+z/bPs+3dFK+/dsfOPKxjHfrxz4N8a774pfGTxVbeJ/2efEPiC98K2tilhdyWGrvpkYvVeSRwYpniZm8uWH5wpBBAz8pA9y8N/EX4LfGm+fwtY3GmeLJbWI6gbO/0eVo4wpEfmDz4gu4eaBxzhj2zQB4N/w1b/wtD/i2f/CBf2R/wlv/ABIvt/8Aa/n/AGT7X+483y/JXft8zdt3LnGMjOaP+TLv+p7/AOEx/wC4b9k+x/8Af7zN/wBq/wBnGzvnj1PxR4t/Ze+HfjWTSNX0jwlo3iHSpYpiIfCzGS3cqssbrJFAQGwysCpyD6EV88/tz/FLwJ8Sv+EO/wCEK13+1f7O+3fa/wDRJ4fL8z7Ps/1qLnOx+mcY57UAY37MGu/8JR+2rp/ib7L9k/tfUtVvvs/mb/K823uX2bsDdjdjOBnHQVs/8FHf+S36N/2LcH/pTc1d/Y++DPxK0f4teDviBqPhvyPDUltLdLe/brdsxT2cgiby1kL/ADGRONuRnnGDX0n8a/F/wB0DxVbWfxUtPD82tPYpLA1/4fa9k+zl5AoDiF8LvEny565OOeQDxL/huf8A6pd/5X//ALnrwDxlrv8AwvL9oaDUPsv/AAj/APwlGpWFjs8z7V9mysNvvzhN/wB3dj5euM969T/ay8X/AAB1/wCHWn2fwrtPD8OtJq8cs7WHh9rKT7OIZgwLmFMrvMfy564OOOPEvgT/AMlv8B/9jJp3/pTHQB2f7TXwL/4Ut/wj/wDxVP8Abv8AbP2n/mH/AGbyfJ8r/po+7Pm+2NvfPHGfBLx1/wAK1+J+keNf7L/tX+zvO/0T7R5PmeZBJF9/a2Mb89DnGPevsz9uf4W+O/iV/wAId/whWhf2r/Z3277X/pcEPl+Z9n2f611znY/TOMc9q8m/Z3/Zu+Imj/GLQtR+IHgGyl8NQ/aPtq3lzZ3URzbyrHuiEjFv3hTHynBweMZoA+pv2cfiv/wuHwPeeJv7B/sT7NqT2P2f7Z9o3bYon37tiYz5uMY7deePjP8AaO/aQ/4XD4Hs/DP/AAhn9ifZtSS++0f2n9o3bYpU2bfKTGfNznPbpzx+gvhvw9oHhqxex8OaHpmjWkkpmeCwtEt42cgAuVQAFsKoz1wB6V+XXxH+DPxK+Hehw634x8N/2ZYT3K2scv263m3SsrMFxHIx+6jHOMcfSgD0Dwb+zf8A8JF+zzP8W/8AhM/svlabf339m/2Zvz9maYbPN80fe8rrs43dDjnF/Zl+On/Clv8AhIP+KW/t3+2fs3/MQ+zeT5Pm/wDTN92fN9sbe+eIfDXhD4/Xfwal17QbvxAvgBbG7lkji8QLFbfZ0Mn2gG384ErlZcrs+bng555L4ZfC3x38Sv7Q/wCEK0L+1f7O8r7X/pcEPl+Zv2f611znY/TOMc9qAPpn/hlL/haH/FzP+E9/sj/hLf8Aie/YP7I8/wCyfa/3/leZ5y79vmbd21c4zgZxR/wnX/DH3/Fs/wCy/wDhNv7T/wCJ79v+0f2d5fmfuPK8vbLnH2bdu3DO/GBjJ9m+B3xS8CR6f4W+Ej67jxrpemxaXeab9kn/AHV1a2+LiPzdnlHaYpPmDlTt4JyM/M3/AAUd/wCS36N/2LcH/pTc0Aff9fM3xt+Bfk/E/V/2gf8AhKd39h+Trv8AYv8AZ+PP+wQRt5Xn+Z8u/wAjG7yzt3dGxz8s/DjW/j/8RNcm0Twd418Z6nfwWzXUkX/CSyQ7YlZVLZklUfedRjOefrW14d1n4uaT8e/D3w8+IfirxNN5ut6faatpN7rcl3bzwTyRFopFEjRyI8cmGU5BDEEdRQB6z/yej/1In/CHf9xL7X9s/wC/Pl7Psv8AtZ39sc+Z/swaF/wi/wC2rp/hn7V9r/sjUtVsftHl7PN8q3uU37cnbnbnGTjPU19meJ/Evwa+Bv2f+0LbRfB/9t7tn9naMy/afJxnd9niP3fNGN398471z/w8+If7Ofif4k2qeDY/D83jC+lnmhuYvDskFzI5jd5XM7QrhinmEktk5I5zyAe2UUUUAeQftZfFDX/hN8OtP8R+HLPTLq7udXjsnS/jd4wjQzOSAjod2Y174wTxR4B+KGv6/wDsr3PxUvLPTI9ai0jUr1YIo3FtvtmnCAqXLbT5S5+bPJwR26b41/FDQPhN4VtvEfiOz1O6tLm+SyRLCNHkDskjgkO6DbiNu+ckcV4lr37UHgH4laHf/DnQtI8TW2reKraTRLGa9toEt457pTDG0jJMzBAzgsVViBnAJ4oA5n4Zf8Zg/wBof8LL/wCJR/wiXlfYP+Ee/ceZ9r3+Z5vn+bnH2aPbt24y2c5GPE/APwv0DX/2qLn4V3l5qceixavqVks8UiC52WyzlCWKFdx8pc/Ljk4A7Znx0+Cnir4Pf2P/AMJNqGi3f9r+f9n/ALOmlfb5Xl7t2+NMZ81cYz0PTv8Aox8Cf+SIeA/+xb07/wBJo6APln4j+NdV/ZM1yH4c/Dm3stV0nUbZdbmm19GmuFnkZoWVWhaJQm23QgFSclucYA+evgp8UNf+E3iq58R+HLPTLq7ubF7J0v43eMIzxuSAjod2Y174wTxX3/8AGv8AaF8F/CbxVbeHPEemeILq7ubFL1HsIIXjCM8iAEvKh3ZjbtjBHNfL/wDwxV8U/wDoP+DP/Ay5/wDkegD03wT8FPCv7RPhi0+MfjXUNa0/X/EG/wC12+kTRRWifZ3a2TYsscjjKQoTlz8xOMDAHjP7YPwU8K/B7/hFv+EZ1DWrv+1/tf2j+0Zon2+V5O3bsjTGfNbOc9B07/Uum+CtV+Hf7F2v+DtbuLK4v9P8N6z5slm7NE3mC4lG0sqn7rjOQOc/Wvlr9j741+Ffg9/wlP8Awk2n61d/2v8AZPs/9nQxPt8rzt27fImM+auMZ6Hp3AJvCf7XfxJ8NeFdJ8OWOieEpLTSrGGygea1uDIyRIEUsROAWwozgAZ7CvM/jX8UNf8Aiz4qtvEfiOz0y1u7axSyRLCN0jKK8jgkO7ndmRu+MAcV91/C39qDwD8RPHeneDtE0jxNb3+oeb5Ul5bQLEvlxPKdxWZj91DjAPOPrXzn/wAFHf8Akt+jf9i3B/6U3NAHs3/DFXws/wCg/wCM/wDwMtv/AJHqlr37L/gH4a6Hf/EbQtX8TXOreFbaTW7GG9uYHt5J7VTNGsipCrFCyAMFZSRnBB5r5t/ZN+KGgfCb4i6h4j8R2ep3Vpc6RJZIlhGjyB2mhcEh3QbcRt3zkjivt/xJ410r4ifsq+K/GOiW97b2GoeG9V8qO8RVlXy454juCsw+8hxgnjH0oA+Wf+G1fin/ANADwZ/4B3P/AMkUf8Nq/FP/AKAHgz/wDuf/AJIr5mr7G/ZL/Z68aaB458GfFS81Pw/Josti16sEU8xudlzZuEBUxBdw81c/Njg4J7gHJf8ADavxT/6AHgz/AMA7n/5Ir7G+Nfwv0D4s+Fbbw54jvNTtbS2vkvUewkRJC6pIgBLo424kbtnIHNdzXkH7WXwv1/4s/DrT/Dnhy80y1u7bV4713v5HSMosMyEAojndmRe2MA80AdN4a+F+gaB8GpfhXZ3mpyaLLY3dk08siG52XJkLkMEC7h5rY+XHAyD3zPgX8FPCvwe/tj/hGdQ1q7/tfyPtH9ozRPt8rzNu3ZGmM+a2c56Dp3/Nn4peCtV+HfjvUfB2t3FlcX+n+V5slm7NE3mRJKNpZVP3XGcgc5+tdP8AAv4KeKvjD/bH/CM6hotp/ZHkfaP7RmlTd5vmbduyN848ps5x1HXsAfdnhr9nrwXoHxll+KlnqfiCTWpb67vWglnhNtvuRIHAURBto81sfNngZJ7nxr/Z68F/FnxVbeI/Eep+ILW7trFLJEsJ4UjKK8jgkPE53ZkbvjAHFfDPwj1u1+C37RkN94pjmvIvDV9fWV4NNAkaRxFNb5j8wpld5B52nHbPFfVn/Davws/6AHjP/wAA7b/5IoA+Ovgp8UNf+E3iq58R+HLPTLq7ubF7J0v43eMIzxuSAjod2Y174wTxW/4b8a6r8RP2qvCnjHW7eyt7/UPEmlebHZoyxL5ckEQ2hmY/dQZyTzn6V6B/wTi/5LfrP/Ytz/8ApTbV9QeJf2hfBegfGWL4V3mmeIJNalvrSyWeKCE22+5EZQljKG2jzVz8ueDgHuAafx0+CnhX4w/2P/wk2oa1af2R5/2f+zpok3eb5e7dvjfOPKXGMdT17fHP7N+iWvhr9uK38OWMk0lppWr6vZQPMQZGSKC6RSxAALYUZwAM9hXo3/BTT/mn3/cS/wDbWu6/Zk/aF8F6/aeCPhXZ6Z4gj1qLSIbJp5YIRbb7a0y5DCUttPlNj5c8jIHYA+kqKKKAOG+Neh/DTX/CttZ/FSfTIdFS+SWBr/UzZR/aAkgUBw6ZbYZPlz0ycccfFmq+ALbRf2otJ8QeAfD16/w10/W9Mu49atFmutNigj8hrmU3ZLJsR1m3sXwm1gSNuB9gftHfCj/hcPgez8M/29/Yn2bUkvvtH2P7Ru2xSps270xnzc5z26c8fP8A/wAJ1/wrv/jEn+y/7T+1/wDEi/4SX7R5O3+0/n837LtbPl/a8bfN+bZ1XdwAfQHifw18Gvjl9n/tC50Xxh/Ym7Z/Z2ss32bzsZ3fZ5R97yhjd/cOO9fH/iD4yfG3Q/iXrHw1+HesXr2GjaldaXo2lWejwXcsdrbO6RxrmJpJNkUYyzFmwpJJ5NfU37MvwL/4Ut/wkH/FU/27/bP2b/mH/ZvJ8nzf+mj7s+b7Y2988eZ+O/hR/wAKT8ca9+0h/b39vfY9SuL7+wvsf2bf9ulaHZ9o3vjZ9p3Z8v5tmMLnIAKXw40zwb4/0ObWf2qJLKz8aw3LW1imv3p0S4OnBVaMrAjQhk817jEm05IYZO3AP2KfjN8SviJ8U9T0Txj4k/tOwg0SW6ji+w28O2VZ4FDZjjU/ddhjOOfpVL/hBf8AhsH/AIuZ/an/AAhP9mf8SL7B9n/tHzPL/f8Am+Zuixn7Tt27TjZnJzgH/CC/8Mff8XM/tT/hNv7T/wCJF9g+z/2d5fmfv/N8zdLnH2bbt2jO/ORjBANr41+JfjK3x+vvCsttrX/Cpbm5s7bVZjoyiyGnSwxC9L3nlZRArTbpPMGznldvHZ+GPgR+zB4o+0f8Izpei639m2/aP7O8SXFx5W7O3dsuDtztbGeuD6V2fg3Xf+F5fs8z6h9l/wCEf/4SjTb+x2eZ9q+zZaa335wm/wC7ux8vXGe9Yv7MvwL/AOFLf8JB/wAVT/bv9s/Zv+Yf9m8nyfN/6aPuz5vtjb3zwAXdE+DfwS+F2qQ+OrTR7Lw7Npm7bqN5rE4ig8xTEdxmlKfMJCoz3YY5xU3iT4dfBb403yeKb630zxZLaxDTxeWGsStHGFJk8s+RKF3DzSeecMO2K+bf2qP2kP8AhItI8a/CT/hDPsvlak1j/aX9p78/ZrtTv8ryh97yum/jd1OOeM/Zx/aQ/wCFPeB7zwz/AMIZ/bf2nUnvvtH9p/Z9u6KJNm3ynzjys5z36ccgHP8A7Juh/DTX/iLqFn8VJ9Mh0VNIklga/wBTNlH9oE0IUBw6ZbYZPlz0ycccdN8YPiR4i8PeLdZ+D/wf1+G48ATxJp+m6bpsUOoLOLqFTNHHMVeV2aWaUcOSCdoxgAQ/tHfs3/8ACnvA9n4m/wCEz/tv7TqSWP2f+zPs+3dFK+/d5r5x5WMY79eOfMvgT/yW/wAB/wDYyad/6Ux0AfQH7H3wB07xF/wlP/C2/h7rUP2f7J/Zv9opd2Gd3nebtwU3/djz1xx0zz7Z8efiZ4a+HfwP1XR/hz428P2PiHQYrXT9PsIr+C6ubcRzxRPGYpC7MyxhwdwJGCTyM1p/tNfHT/hS3/CP/wDFLf27/bP2n/mIfZvJ8nyv+mb7s+b7Y2988fM3xt+BfnfDDV/2gf8AhKdv9ueTrv8AYv8AZ+fI+3zxt5Xn+Z82zz8bvLG7b0XPABxn/DUfx2/6Hn/yk2X/AMZr6z/aO+KerzeB7NfgR4ostf8AEo1JDdW2gCDVbhLPypd7tEokKoJPJBfaMFlGfmwfmb9nH9m//hcPge88Tf8ACZ/2J9m1J7H7P/Zn2jdtiiffu81MZ83GMduvPHGfs4/Ff/hT3ji88Tf2D/bf2nTXsfs/2z7Pt3SxPv3bHzjysYx368cgH1z8PPhF4P8AHvw2tfiN8dfDM3/CYXMU82uXmpT3GmsiQyPHG8kSPHHEqwRx87VBA3HOST6N8C/DXwa8O/2x/wAKkudFm+0eR/aX9nay1/jb5nlbsyvs+9Jjpnnrjj5//wCGrf8AhaH/ABbP/hAv7I/4S3/iRfb/AO1/P+yfa/3Hm+X5K79vmbtu5c4xkZzR/wAmXf8AU9/8Jj/3Dfsn2P8A7/eZv+1f7ONnfPABp/tM/Dr4Laj4a8W3Pge30zVfifcX3mix07WJbu/e4N0pugLRZWywTziyhPlAY4G3j4z8SeHtf8NXyWPiPQ9T0a7kiEyQX9o9vIyEkBwrgErlWGemQfSu68G/Ff8A4R39oaf4t/2D9q83Ur++/s37Zsx9pWYbPN2H7vm9dnO3oM8e/wD/AAgv/DYP/FzP7U/4Qn+zP+JF9g+z/wBo+Z5f7/zfM3RYz9p27dpxszk5wADjP+CcX/Jb9Z/7Fuf/ANKbauf/AGrrnWbL9sfV7zw4sza1BfaZLpyww+dIbhbW2MQVCDvbeFwuDk8YNfUH7OP7N/8Awp7xxeeJv+Ez/tv7Tpr2P2f+zPs+3dLE+/d5r5x5WMY79eOTxl+zf/wkX7Q0Hxb/AOEz+y+VqVhff2b/AGZvz9mWEbPN80fe8rrs43dDjkA+TPibB+0d8Sv7P/4TXwj4z1X+zvN+yf8AFLvD5fmbN/8AqoVznYnXOMcd6n/Y0tLqw/ar8M2N9bTWt3bS38M8E0ZSSJ1s7gMjKeVYEEEHkEV+ldeAeDf2b/8AhHf2hp/i3/wmf2rzdSv77+zf7M2Y+0rMNnm+afu+b12c7egzwAe/0UUUAeQftZaH8S9f+HWn2fwrn1OHWk1eOWdrDUxZSfZxDMGBcumV3mP5c9cHHHHy/wCAvgN8fP8Ahc/hjxf4v0C9u/s2t2N1f395rVtcS+VFLGSzEzM7bUTAAycAAdhX1B+1l8UNf+E3w60/xH4cs9Muru51eOydL+N3jCNDM5ICOh3ZjXvjBPFfL/8Aw2r8U/8AoAeDP/AO5/8AkigD6A/bB8NfGXxF/wAIt/wqS51qH7P9r/tL+ztZWwzu8nyt2ZU3/dkx1xz0zzp/Frwb438T/siJ4NSzm1HxhLpGlxXMM15GZJLiKS3actK77Wb5HJYsckcE55+bP+G1fin/ANADwZ/4B3P/AMkUf8Nq/FP/AKAHgz/wDuf/AJIoAzPDfwT/AGqvDVi9j4ctPEGjWkkpmeCw8UQW8bOQAXKpcAFsKoz1wB6V9s/GvXPhpoHhW2vPipBpk2ivfJFAt/phvY/tBSQqQgR8NsEnzY6ZGeefjn/htX4p/wDQA8Gf+Adz/wDJFfY3xr+F+gfFnwrbeHPEd5qdraW18l6j2EiJIXVJEAJdHG3Ejds5A5oA0/hbqfg3WPAmnaj8P47KLw1N5v2JbOyNrEMSusm2IqpX94Hz8oycnnOapfE34peBPhr/AGf/AMJrrv8AZX9o+b9k/wBEnm8zy9m//VI2Mb064znjvXyb42+Nfir9nbxPd/BzwVp+i6hoHh/Z9kuNXhllu3+0Ity+9opI0OHmcDCD5QM5OSfGfjp8a/FXxh/sf/hJtP0W0/sjz/s/9nQypu83y927fI+ceUuMY6nr2APvjxRp/wACrbwVJ8T/ABB4T8JSaLeRRahLqk3h5JZJhcsuyRl8oyFnaRScjOWycc18tfGv4ar8ZPFVt4n/AGefCumXvhW1sUsLuSwig0yMXqvJI4MU3lMzeXLD84UgggZ+Ugev/GT/AJR52P8A2Lehf+jLSj/gnF/yRDWf+xkn/wDSa2oA+IPEnjfxp4lsUsfEfi/xBrNpHKJkgv8AUpriNXAIDhXYgNhmGeuCfWvqb9mj4h/s5+GPhX4bTxlH4fh8YWMs001zL4dknuY3FzI8TidYWywTyyCGyMAcY45n9rL9nrwX8Jvh1p/iPw5qfiC6u7nV47J0v54XjCNDM5ICRId2Y174wTxXz/8ADXRLXxL8RfDXhy+kmjtNV1e1sp3hIEipLMqMVJBAbDHGQRnsaAPef25/il4E+JX/AAh3/CFa7/av9nfbvtf+iTw+X5n2fZ/rUXOdj9M4xz2r2b4yf8o87H/sW9C/9GWlH/DFXws/6D/jP/wMtv8A5Hrz/RvGuq+P/iHJ+yvrFvZQeCrW5n0RL60Rl1IwacHeBjIzNFvY2se8+Vg5bAXIwAH7FPxm+Gvw7+Fmp6J4x8Sf2Zfz63LdRxfYbibdE0EChsxxsPvIwxnPH0rA/ay8X/AHX/h1p9n8K7Tw/DrSavHLO1h4fayk+ziGYMC5hTK7zH8ueuDjjjz/APay+F+gfCb4i6f4c8OXmp3Vpc6RHeu9/IjyB2mmQgFEQbcRr2zknmvp/wD4Yq+Fn/Qf8Z/+Blt/8j0AUv2e/h9omtfsfWur6R4S0WbxrLpuqHTNTFnCl6t4s9wtu6XDAMjqwj2vuG3aCCMV4b4n+BH7T/ij7P8A8JNpeta39m3fZ/7R8SW9x5W7G7bvuDtztXOOuB6V6npnxQ1/4N/G3Rf2evDFnpl54VtNXsbKO71GN5L8pePHNKS6Oke4NcOF/d4AC5B5J9G/bB+Nfir4Pf8ACLf8Izp+i3f9r/a/tH9owyvt8rydu3ZImM+a2c56Dp3APgzRvAXizWPiHJ8P9O0nz/EsdzPatZfaIlxLAHMq+YzBPlEb87sHHGcivYPDfwT/AGqvDVi9j4ctPEGjWkkpmeCw8UQW8bOQAXKpcAFsKoz1wB6VS/ZR1u68S/tj6R4jvo4Y7vVb7U72dIQRGry2ty7BQSSFyxxkk47mvoD9rL9oXxp8JviLp/hzw5pnh+6tLnSI713v4JnkDtNMhAKSoNuI17ZyTzQB88/Eex/ad+Hehw634x8T+M9MsJ7lbWOX/hKjNulZWYLiOdj91GOcY4+lXfBPh39qzxp4YtPE3hnxB4zv9JvN/wBnuP8AhLRHv2OyN8r3AYYZWHIHT0r3/wD4KO/8kQ0b/sZIP/Sa5r5z+Fv7UHj74d+BNO8HaJpHhm4sNP8AN8qS8tp2lbzJXlO4rMo+85xgDjH1oA9z+BfizxV8E/7Y/wCGkPEutWP9s+R/YX9o30uq7/J8z7Rt8lpfLx5sGd23dkYztOOF+BXxM8S+Kf2z0t7fxt4g1Hwrfavq0tnZy38/2Z7fybl4QIXOFUAIQpUbcDgYroPhl/xmD/aH/Cy/+JR/wiXlfYP+Ee/ceZ9r3+Z5vn+bnH2aPbt24y2c5GPOf2b9EtfDX7cVv4csZJpLTStX1eygeYgyMkUF0iliAAWwozgAZ7CgD9EqKKKAPmb/AIKO/wDJENG/7GSD/wBJrmug/ZR1u18NfscaR4jvo5pLTSrHU72dIQDIyRXVy7BQSAWwpxkgZ7iuf/4KO/8AJENG/wCxkg/9Jrmj4N/8o877/sW9d/8ARl3QAf8ADavws/6AHjP/AMA7b/5Irpvhb+1B4B+InjvTvB2iaR4mt7/UPN8qS8toFiXy4nlO4rMx+6hxgHnH1r82q9m/Yl/5Od8I/wDb7/6RT0Adn/wUd/5Lfo3/AGLcH/pTc19QftZfC/X/AIs/DrT/AA54cvNMtbu21eO9d7+R0jKLDMhAKI53ZkXtjAPNfL//AAUd/wCS36N/2LcH/pTc19QftZa58S9A+HWn3nwrg1ObWn1eOKdbDTBeyfZzDMWJQo+F3iP5sdcDPPIB8GXGiXXwW/aA0ux8UyQ3kvhrV7C9vDppMiyIDFcYj8wJlthA52jPfHNfXP8Aw2r8LP8AoAeM/wDwDtv/AJIq78Lfgv4Z+JngTTvG/wAZvCF7deO9S83+1ZbyS5sJW8uV4od0EbRon7mOIDCDIAJySSfJv2wfgDp3h3/hFv8AhUnw91qb7R9r/tL+zku7/G3yfK3ZL7PvSY6Z5644APbP2rtbtfEv7HGr+I7GOaO01Wx0y9gSYASKkt1bOoYAkBsMM4JGe5rwD9k39oXwX8Jvh1qHhzxHpniC6u7nV5L1HsIIXjCNDCgBLyod2Y27YwRzWL8H/iR4i8Q+LdG+D/xg1+G38AQRPp+pabqUUOnrALWFjDHJMFSVGWWGIcuCSNpzkg+8/wDCtP2Nv+gl4M/8LOT/AOSaAO6+Cn7Qvgv4s+Krnw54c0zxBa3dtYveu9/BCkZRXjQgFJXO7Mi9sYB5o8S/tC+C9A+MsXwrvNM8QSa1LfWlks8UEJtt9yIyhLGUNtHmrn5c8HAPf5s/Yp0nVfhr8U9T134jaZe+DdJn0SW0hvtfgawt5J2ngdYlkmCqXKo7BQckIxxgGsX4k6tpWuft8aXqmianZanYT+JND8q6s51mik2raqdrqSDhgQcHqCKAPQP+Cmn/ADT7/uJf+2tes6l4K1X4ifsXaB4O0S4sre/1Dw3o3lSXjssS+WLeU7iqsfuocYB5x9a4D/god4T8VeKP+EG/4Rnw1rWt/Zv7Q+0f2dYy3Hlbvs23dsU7c7Wxnrg+lfPNh+0J8cfCdjb+FofFM2mxaNEunpZzaRaiS2EI8sRtvh3bl24O7nI55oA95+HHjXSv2TNDm+HPxGt73VdW1G5bW4ZtARZrdYJFWFVZpmiYPut3JAUjBXnOQOm/4bV+Fn/QA8Z/+Adt/wDJFfI3iSb4wfGm+TxTfaN4g8WS2sQ08XlhopaOMKTJ5Z8iMLuHmk884YdsV7z+2t8Gfhr8O/hZpmt+DvDf9mX8+txWskv264m3RNBOxXEkjD7yKc4zx9aAPQP+G1fhZ/0APGf/AIB23/yRXpvwL+NfhX4w/wBsf8Izp+tWn9keR9o/tGGJN3m+Zt27JHzjymznHUde3jP7LnwE+E/jT4E+HfE3ibwp9v1a8+1faLj+0LqPfsupUX5UkCjCqo4A6etezeGPDXwa+Bv2j+z7nRfB/wDbe3f/AGjrLL9p8nONv2iU/d8052/3xntQB8Tab410r4d/to6/4x1u3vbiw0/xJrPmx2aK0reYbiIbQzKPvOM5I4z9K+jP+G1fhZ/0APGf/gHbf/JFfPPgPQfC/wARP219S0jV4odZ8Par4g1iYiG5YR3CBbmWN1kiYErlVYFTgj1Br65/4Zc+BP8A0I3/AJVr3/49QB82/tZftC+C/iz8OtP8OeHNM8QWt3bavHeu9/BCkZRYZkIBSVzuzIvbGAea0/gp8a/Cs3wBsfgQun60PEutW15oltdGGL7Ek99NKsTO/mbwgM6biEJGDgNxn5s8SeCPGnhqxS+8R+EPEGjWkkohSe/02a3jZyCQgZ1ALYVjjrgH0r6z/ZL8IfAG78DeDNe1678Pr4/W+aWOOXxA0Vz9oS8f7OBb+cAWwsWF2fNxwc8gHzz8dPgp4q+D39j/APCTahot3/a/n/Z/7Omlfb5Xl7t2+NMZ81cYz0PTv7z+yX+z1400Dxz4M+Kl5qfh+TRZbFr1YIp5jc7LmzcICpiC7h5q5+bHBwT3m/4Kaf8ANPv+4l/7a17z+z7438F3vwx8C+HLPxf4fudaXw/ZQtp0WpQvch47VC6GINu3LtbIxkbTnpQB6nRRRQB8zf8ABR3/AJIho3/YyQf+k1zW1+zBoX/CUfsVaf4Z+1fZP7X03VbH7R5e/wArzbi5TftyN2N2cZGcdRVz9tbwF4s+Inws0zRPB2k/2nfwa3FdSRfaIodsSwTqWzIyj7zqMZzz9a+ZtE+D37Wmh6XDpeiDxNplhBu8q1s/FcMMUe5ix2otwAMsSTgdSTQB6B/wwx/1VH/ygf8A3RXZ/BL9lL/hWvxP0jxr/wAJ7/av9ned/on9keT5nmQSRff85sY356HOMe9eM/8ACtP2yf8AoJeM/wDws4//AJJo/wCFaftk/wDQS8Z/+FnH/wDJNAB/wUd/5Lfo3/Ytwf8ApTc19/1+cHiT9n39pXxLfJfeI9C1PWbuOIQpPf8AiG1uJFQEkIGeckLlmOOmSfWvsD9rLQ/iXr/w60+z+Fc+pw60mrxyztYamLKT7OIZgwLl0yu8x/Lnrg444AOf8ZftIf8ACO/tDQfCT/hDPtXm6lYWP9pf2nsx9pWE7/K8o/d83pv529Rnj3+vAPBvws1dv2eZ5fFXheyu/i1/Zt+YdVuzBPqQvA032Nxekkh1XyNj+Z8m1eV28H7H3hr4y+Hf+Ep/4W3c61N9o+yf2b/aOsrf42+d5u3Er7PvR56Z4644APGf2qP2b/8AhHdI8a/Fv/hM/tXm6k19/Zv9mbMfabtRs83zT93zeuznb0GeOM/Zx/Zv/wCFw+B7zxN/wmf9ifZtSex+z/2Z9o3bYon37vNTGfNxjHbrzx9mftR+Ftd8afAnxF4Z8M2P2/Vrz7L9nt/NSPfsuonb5nIUYVWPJHT1rmP2KfAXiz4d/CzU9E8Y6T/Zl/Prct1HF9oim3RNBAobMbMPvIwxnPH0oA8m/wCE6/4bB/4tn/Zf/CE/2Z/xPft/2j+0fM8v9x5Xl7YsZ+07t2442Ywc5G14E/Y1/wCEX8caD4m/4WP9r/sjUre++z/2Js83ypVfZu887c7cZwcZ6Guz/aO+Fmrw+B7NvgR4XstA8SnUkF1c6AYNKuHs/Kl3o0qmMshk8klNxyVU4+XI7/8AZ30zxlo/wd0LTviBJey+JYftH21ry9F1Kc3ErR7pQzBv3ZTHzHAwOMYoA9Ar4a/ao/Zv/wCEd0jxr8W/+Ez+1ebqTX39m/2Zsx9pu1GzzfNP3fN67OdvQZ49m/bB8NfGXxF/wi3/AAqS51qH7P8Aa/7S/s7WVsM7vJ8rdmVN/wB2THXHPTPPmXwS+GP7QUnxP0hPi3DrWseCj539pWer6/FqFpL+4k8rfbmZw+JfLI+U4YBuMZAB5n+zj+0h/wAKe8D3nhn/AIQz+2/tOpPffaP7T+z7d0USbNvlPnHlZznv045P2jv2kP8AhcPgez8M/wDCGf2J9m1JL77R/af2jdtilTZt8pMZ83Oc9unPH3L/AMKn+Fn/AETTwZ/4Irb/AOIr5g/ZN/Z48VaB8RdQvPip8P8ATJtFfSJIoFv3s72P7QZoSpCBnw2wSfNjpkZ55AMz9lf9pD/hHdI8FfCT/hDPtXm6ktj/AGl/aezH2m7Y7/K8o/d83pv529RnjZ/4Kaf80+/7iX/trR4w+AfjWP8Aa10/xZ4T8EWVn4Ktdb0q6R7Oe1t4ooohAZmWEOGGGWQkBck5Iznns/25/hb47+JX/CHf8IVoX9q/2d9u+1/6XBD5fmfZ9n+tdc52P0zjHPagD5m/Yl/5Od8I/wDb7/6RT19Z/tHftIf8Ke8cWfhn/hDP7b+06al99o/tP7Pt3Syps2+U+ceVnOe/Tjn5Z0T9nL9ovQ9Uh1TRPDF7pl/Bu8q6s9dtIZY9ylTtdZwRlSQcHoSKm8Sfs+/tK+Jb5L7xHoWp6zdxxCFJ7/xDa3EioCSEDPOSFyzHHTJPrQBP+0d+0h/wuHwPZ+Gf+EM/sT7NqSX32j+0/tG7bFKmzb5SYz5uc57dOeOz/ZX/AGb/APhItI8FfFv/AITP7L5WpLff2b/Zm/P2a7YbPN80fe8rrs43dDjnzL/hlz47f9CN/wCVay/+PV9zfsueFtd8F/Anw74Z8TWP2DVrP7V9ot/NSTZvupXX5kJU5VlPBPX1oAxf2mvgX/wun/hH/wDiqf7C/sb7T/zD/tPned5X/TRNuPK987u2OeM+CX7KX/CtfifpHjX/AIT3+1f7O87/AET+yPJ8zzIJIvv+c2Mb89DnGPetr9sHw18ZfEX/AAi3/CpLnWofs/2v+0v7O1lbDO7yfK3ZlTf92THXHPTPPn/7O/gf9pfR/jFoWo/EC98TS+GoftH21bzxMl1Ec28qx7ohOxb94Ux8pwcHjGaAPr+iiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Z \ No newline at end of file diff --git a/tests/Output/image/img2.png.uri b/tests/Output/image/img2.png.uri deleted file mode 100644 index b62dd8cb0..000000000 --- a/tests/Output/image/img2.png.uri +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMMAAADDCAIAAACwHTL9AAAABnRSTlMA/wD/AP83WBt9AAAE6UlEQVR4nO3dQY4rKRBF0a5W73/L1XMGWIgLmS6dM/tyOZ22n1CYH8DP7+/vP7Dt36dvgD9CkmhIEg1JovHf8O+fn587Lzyv9HduY7jy0qXmzw1/ncyvPL/ncx/dkuE2jEk0JImGJNGQJBpjxT04V2PuWCo5rxXg4evOLf0OuPYNGpNoSBINSaIhSTQ+VNyDnXL1nJ1CeOlSO3+85Fq1vnQbc8YkGpJEQ5JoSBKNtYr7mnnJea7Enj+6VIF+RWdIyJhEQ5JoSBINSaLx0or7XJvz3LV+lb/HmERDkmhIEg1JorFWcZ9rFNnpTd7plNh5R9e6Wa61wO8wJtGQJBqSREOSaHyouF8yb3uuHn9qMeS11732DRqTaEgSDUmiIUk0ft65H/e5CeKndiV85+ccMibRkCQakkRDkmh82I/7XC9EOG8bLmicu/YGd27jqcLfmERDkmhIEg1JolHOcZ9b0PgVPdHn9gEfXLvU0hs0JtGQJBqSREOSaJRz3IN39kQPwpt8ydYl576yOWMSDUmiIUk0JInGOMd9bkXfud6Pp2bP586ddbnkWv+4MYmGJNGQJBqSRGOc4762mfU55/qal95gOJt8bSeTnbsyJtGQJBqSREOSaGydgLNUkV1b0XetuyM8VfKp/VXCSxmTaEgSDUmiIUk01rpKtl7pWFvFta6SnSsPwnueO9c1ZI6bIySJhiTRkCQaa3PcYfPD0gvNr/xU0/eSp7YfudY/bkyiIUk0JImGJNG4d8r7tRaUa7sDDsKfFEueWrA6MCbRkCQakkRDkmh82I/7XBPCuYnpp5ou5rfxFfssWjnJ8ySJhiTRkCQaH+a4l0qwc5uEnCv8l1536a6eWu54bVJ7YEyiIUk0JImGJNEoV06eK/2eOonm2p7a4UrRp37ZGJNoSBINSaIhSTQ+7Md9rt14aUZ4x7W55qfWZIZXnr+F+aPGJBqSREOSaEgSjQ993B+efKtjeunKg3ce2PhUg/y5SxmTaEgSDUmiIUk0Dp7y/geK6JfMgO8497GruDlCkmhIEg1JorF1yvtSz8m5w8iXnnvtrMu5p2r5c9+vMYmGJNGQJBqSRGOsuM8tSvyKHajPVa/z5+587DvFe7jfoTGJhiTRkCQakkRjaz/u+R8Prm3pNxd2Ls+vPH/utd8fS7exRMXNEZJEQ5JoSBKND3PcO3Xiua31nvqVsPTc8K6eOgFnflcDYxINSaIhSTQkiUY5x73z3GvHuFzb2ePaXt7z515jTKIhSTQkiYYk0Vjr457/8U5NHfZEz1837AFfcm2e+tyaTH3c3CBJNCSJhiTR2NqPO/QVTReDa+0cg6f+42Gg4uYISaIhSTQkicbWHPeO+Rz3udu4dp76ud8Qc9cWuw6MSTQkiYYk0ZAkGmPFPTi3ScjSo0+dAT84t79KuAZ153V39h00JtGQJBqSREOSaHyouAfnlvDtlH4v2bTvWvvK4Nx+3E7A4QGSREOSaEgSjbWK+5ydXoiwj2LJtf0Ol648uPafB8YkGpJEQ5JoSBKNt1TcT02IX9tZcEe4JjP8sWLlJEdIEg1JoiFJNNYq7msbm4RH7VybXN5pgn7JEZRLjw6MSTQkiYYk0ZAkGh8q7mtbl7ykTL52QOU7N+BeurI5bo6QJBqSREOSaLxlP26+nTGJhiTRkCQakkTjf7A1wcz7nDmoAAAAAElFTkSuQmCC \ No newline at end of file diff --git a/tests/Output/markup/str1.html b/tests/Output/markup/str1.html deleted file mode 100644 index c56cbc92f..000000000 --- a/tests/Output/markup/str1.html +++ /dev/null @@ -1,21 +0,0 @@ -

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

diff --git a/tests/Output/markup/str1.svg b/tests/Output/markup/str1.svg deleted file mode 100644 index cfbef7ff9..000000000 --- a/tests/Output/markup/str1.svg +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/Output/markup/str2.html b/tests/Output/markup/str2.html deleted file mode 100644 index cf29d1fa9..000000000 --- a/tests/Output/markup/str2.html +++ /dev/null @@ -1,21 +0,0 @@ -

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

diff --git a/tests/Output/markup/str2.svg b/tests/Output/markup/str2.svg deleted file mode 100644 index 249099a71..000000000 --- a/tests/Output/markup/str2.svg +++ /dev/null @@ -1,366 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/Output/markup/str3.html b/tests/Output/markup/str3.html deleted file mode 100644 index b70b04735..000000000 --- a/tests/Output/markup/str3.html +++ /dev/null @@ -1,37 +0,0 @@ -

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

diff --git a/tests/Output/markup/str4.html b/tests/Output/markup/str4.html deleted file mode 100644 index 32cd2eb44..000000000 --- a/tests/Output/markup/str4.html +++ /dev/null @@ -1,37 +0,0 @@ -

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

diff --git a/tests/Output/string/str1.json b/tests/Output/string/str1.json deleted file mode 100644 index 8d41207b6..000000000 --- a/tests/Output/string/str1.json +++ /dev/null @@ -1 +0,0 @@ -[[true,true,true,true,true,true,true,false,false,false,true,true,true,false,true,true,true,true,true,true,true],[true,false,false,false,false,false,true,false,true,true,true,false,false,false,true,false,false,false,false,false,true],[true,false,true,true,true,false,true,false,false,true,false,false,true,false,true,false,true,true,true,false,true],[true,false,true,true,true,false,true,false,false,true,true,true,false,false,true,false,true,true,true,false,true],[true,false,true,true,true,false,true,false,true,true,false,true,true,false,true,false,true,true,true,false,true],[true,false,false,false,false,false,true,false,false,true,false,true,false,false,true,false,false,false,false,false,true],[true,true,true,true,true,true,true,false,true,false,true,false,true,false,true,true,true,true,true,true,true],[false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false],[true,false,true,false,true,false,true,false,false,true,false,false,true,false,false,false,true,false,false,true,false],[true,true,true,true,true,false,false,false,false,true,true,true,false,true,false,true,false,false,true,true,true],[true,true,false,true,true,true,true,true,false,false,true,true,false,true,true,true,false,true,false,true,true],[true,true,false,false,true,true,false,false,true,false,true,true,true,true,false,true,true,false,false,true,true],[true,false,true,false,false,true,true,true,false,true,false,true,false,true,true,true,false,false,false,true,true],[false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,true,true,true],[true,true,true,true,true,true,true,false,false,true,true,false,true,false,false,false,true,true,false,true,true],[true,false,false,false,false,false,true,false,false,true,true,false,false,false,true,true,true,false,false,true,true],[true,false,true,true,true,false,true,false,true,true,true,false,true,false,true,false,true,false,false,true,true],[true,false,true,true,true,false,true,false,false,true,false,true,false,true,false,false,true,true,false,true,false],[true,false,true,true,true,false,true,false,true,true,false,true,false,true,true,true,true,false,false,false,true],[true,false,false,false,false,false,true,false,false,false,false,true,true,true,false,false,false,false,false,true,false],[true,true,true,true,true,true,true,false,true,false,true,true,false,true,true,false,true,false,false,true,true]] \ No newline at end of file diff --git a/tests/Output/string/str1.txt b/tests/Output/string/str1.txt deleted file mode 100644 index f69a1bdba..000000000 --- a/tests/Output/string/str1.txt +++ /dev/null @@ -1,21 +0,0 @@ -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ• -๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ• -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ• -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด diff --git a/tests/Output/string/str2.json b/tests/Output/string/str2.json deleted file mode 100644 index a588641e8..000000000 --- a/tests/Output/string/str2.json +++ /dev/null @@ -1 +0,0 @@ -[[true,true,true,true,true,true,true,false,false,true,true,true,true,false,true,true,true,false,true,false,false,false,false,false,true,false,true,true,false,false,true,true,true,true,true,true,true],[true,false,false,false,false,false,true,false,false,true,true,false,false,true,false,true,true,false,false,true,false,true,false,true,true,false,true,true,true,false,true,false,false,false,false,false,true],[true,false,true,true,true,false,true,false,false,true,true,false,true,false,true,false,false,true,false,true,true,false,false,true,true,false,false,true,true,false,true,false,true,true,true,false,true],[true,false,true,true,true,false,true,false,false,false,true,true,false,true,false,false,true,true,true,false,false,false,true,true,false,false,true,true,true,false,true,false,true,true,true,false,true],[true,false,true,true,true,false,true,false,false,true,false,true,false,false,false,false,true,false,false,true,false,true,true,true,false,false,true,true,true,false,true,false,true,true,true,false,true],[true,false,false,false,false,false,true,false,true,false,false,false,true,true,true,false,false,false,false,true,true,false,true,false,false,true,false,true,true,false,true,false,false,false,false,false,true],[true,true,true,true,true,true,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,true,true,true,true,true,true],[false,false,false,false,false,false,false,false,false,true,false,false,true,false,true,false,false,true,true,true,true,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,true,false,true,true,false,true,true,false,true,false,false,true,true,true,true,true,false,true,false,false,true,true,true,true,true,false,true,false,true,false,false,false,false,false],[false,false,false,false,true,true,false,false,false,false,true,false,false,true,false,true,true,true,true,true,false,true,false,true,true,true,false,false,true,true,true,false,false,true,true,false,true],[true,false,false,true,true,false,true,false,true,true,true,false,true,false,true,true,false,true,false,true,false,false,true,true,true,true,false,false,true,true,false,false,true,true,false,true,true],[true,false,true,false,true,true,false,false,false,true,true,true,false,false,false,false,true,false,false,true,true,true,false,true,true,true,false,true,true,true,true,false,false,false,false,false,false],[false,false,true,false,false,false,true,true,true,true,true,true,false,true,false,true,false,false,false,false,false,true,true,true,false,true,false,false,true,false,true,false,false,true,false,true,true],[false,false,true,true,true,false,false,true,false,false,false,false,false,false,true,true,false,false,false,false,true,true,false,false,true,false,true,false,true,true,true,false,false,true,true,true,true],[true,false,false,false,false,false,true,true,true,false,true,true,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,true,true,true,false,true,false,true,false,true],[true,false,true,true,false,true,false,false,false,true,false,true,true,true,true,false,true,false,false,true,true,true,true,false,false,true,true,true,false,false,true,true,true,false,false,true,true],[true,false,false,false,false,true,true,true,false,false,true,false,false,true,false,true,true,false,false,true,true,false,false,false,true,false,false,false,false,true,true,false,false,false,true,true,true],[false,true,false,true,false,true,false,true,true,true,false,false,false,true,true,true,false,true,false,true,false,false,true,false,false,true,true,false,true,false,true,true,true,true,false,true,true],[false,false,true,true,true,false,true,false,false,false,true,false,true,false,true,false,false,true,true,true,true,false,false,false,true,true,true,false,true,true,false,false,false,true,false,true,true],[true,false,false,false,true,false,false,false,true,true,false,true,true,false,true,true,true,true,false,true,true,true,false,true,true,false,true,true,false,false,true,false,false,false,true,false,true],[false,true,false,false,true,false,true,false,false,false,false,true,true,false,false,false,true,true,false,true,true,false,false,true,true,true,true,true,false,false,true,true,true,true,false,false,false],[false,false,false,true,true,true,false,true,false,true,true,true,false,false,true,true,true,true,false,false,false,false,false,false,true,false,true,false,true,false,true,true,false,true,false,true,true],[true,false,true,false,true,false,true,true,true,false,false,true,true,true,true,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,false,false,true],[false,false,true,true,false,false,false,true,true,false,true,false,false,false,true,true,false,false,true,true,true,false,false,false,true,true,true,true,true,false,false,true,false,false,false,true,false],[true,true,false,true,false,false,true,false,false,false,true,true,true,false,true,true,true,false,true,true,false,false,false,false,false,true,true,false,true,true,true,true,false,true,false,true,true],[false,true,false,false,true,true,false,true,false,true,false,false,false,false,false,true,false,true,true,false,false,true,true,false,true,false,false,false,false,true,true,true,false,true,true,true,true],[true,false,true,false,true,false,true,true,false,false,true,false,false,true,false,true,true,true,false,false,true,true,true,true,false,false,true,false,true,false,true,true,false,true,false,false,true],[false,true,false,false,false,true,false,false,true,false,false,false,true,true,true,false,true,true,false,true,false,true,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false],[true,true,false,true,false,true,true,false,false,false,false,false,false,false,true,true,true,false,false,true,false,true,false,false,true,false,true,true,true,true,true,true,true,true,true,false,true],[false,false,false,false,false,false,false,false,true,false,false,true,false,true,false,true,true,true,true,true,false,true,true,true,true,false,false,false,true,false,false,false,true,false,false,true,true],[true,true,true,true,true,true,true,false,false,true,true,true,false,true,false,false,true,true,false,false,true,false,true,false,false,true,false,true,true,false,true,false,true,true,true,false,true],[true,false,false,false,false,false,true,false,true,true,false,false,false,true,false,false,false,false,false,true,false,true,false,false,false,false,false,false,true,false,false,false,true,false,true,true,true],[true,false,true,true,true,false,true,false,false,true,true,true,false,true,true,true,false,true,false,true,false,false,true,true,true,true,true,false,true,true,true,true,true,true,false,true,false],[true,false,true,true,true,false,true,false,true,false,false,true,true,false,false,true,false,true,false,true,true,true,true,false,true,false,false,false,true,false,false,true,true,false,false,true,false],[true,false,true,true,true,false,true,false,false,false,false,false,true,true,true,false,true,true,false,false,true,true,false,false,true,false,false,true,true,false,false,false,true,true,false,true,true],[true,false,false,false,false,false,true,false,false,true,false,false,false,true,false,true,false,false,false,false,false,true,true,true,true,true,false,true,true,false,true,true,true,false,false,false,false],[true,true,true,true,true,true,true,false,true,false,true,false,false,true,false,false,true,false,false,true,true,false,false,true,false,true,false,true,true,true,true,true,true,false,false,true,true]] \ No newline at end of file diff --git a/tests/Output/string/str2.txt b/tests/Output/string/str2.txt deleted file mode 100644 index 90005607f..000000000 --- a/tests/Output/string/str2.txt +++ /dev/null @@ -1,37 +0,0 @@ -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ• -๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ• -โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ• -โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด -โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ• -โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด -โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ• -๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด -โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ• -๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ• -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด diff --git a/tests/Output/string/str3.txt b/tests/Output/string/str3.txt deleted file mode 100644 index 3a29219bf..000000000 --- a/tests/Output/string/str3.txt +++ /dev/null @@ -1,49 +0,0 @@ -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ• -๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ• -โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ• -๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ• -โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ• -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ• -โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ• -โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ• -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ• -๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ• -๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ• -โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ• -โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด -โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด -โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด -โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ• -โญ•โญ•โญ•โญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ• -๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ• -๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ• -๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ดโญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ดโญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด๐Ÿ”ดโญ•โญ•โญ•โญ•๐Ÿ”ด๐Ÿ”ด๐Ÿ”ด diff --git a/tests/QRCodeTest.php b/tests/QRCodeTest.php index aceb91d12..915d36646 100644 --- a/tests/QRCodeTest.php +++ b/tests/QRCodeTest.php @@ -1,118 +1,37 @@ - * @copyright 2015 Smiley + * @copyright 2017 Smiley * @license MIT */ namespace chillerlan\QRCodeTest; -use chillerlan\GoogleAuth\Authenticator; -use chillerlan\QRCode\Output\{QRImage, QRImageOptions, QRString}; -use chillerlan\QRCode\{QRCode, QROptions}; -use ReflectionClass; -use PHPUnit\Framework\TestCase; +use chillerlan\QRCode\Output\QRMarkupOptions; +use chillerlan\QRCode\QRCode; -class QRCodeTest extends TestCase{ +abstract class QRCodeTest extends QRTestAbstract{ - /** - * @var \chillerlan\QRCode\QROptions - */ - protected $options; + protected $FQCN = QRCode::class; - /** - * @var \chillerlan\QRCode\Output\QROutputInterface - */ - protected $output; - - /** - * @var \ReflectionClass - */ - protected $reflectionClass; - - protected function setUp(){ - $this->options = new QROptions; - $this->output = new QRString; - $this->reflectionClass = new ReflectionClass(QRCode::class); - } - - public function testInstance(){ - $this->assertInstanceOf(QRString::class, $this->output); - $this->assertInstanceOf(QROptions::class, $this->options); - $this->assertInstanceOf(QRCode::class, $this->reflectionClass->newInstanceArgs(['foobar', $this->output, $this->options])); - } - - public function stringDataProvider(){ + public function optionsDataProvider(){ return [ - ['1234567890', QRCode::TYPE_01], - ['ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', QRCode::TYPE_03], - ['#\\', QRCode::TYPE_01], - ['่Œ—่ท', QRCode::TYPE_01], + [QRMarkupOptions::class], +# [], ]; } /** - * @dataProvider stringDataProvider + * @dataProvider optionsDataProvider */ - public function testDataCoverage($data){ - (new QRCode($data, $this->output))->getRawData(); - $this->markTestSkipped('code coverage'); + public function testInstance($options){ + $q = $this->reflection->newInstanceArgs([new $options]); + $this->assertInstanceOf($this->FQCN, $q); +# print_r($q->render('test')); } - - /** - * @dataProvider stringDataProvider - */ - public function testTypeAndErrorcorrectlevelCoverage($data){ - - foreach(range(1, 10) as $type){ - foreach(range(0, 3) as $eclevel){ - $this->options->typeNumber = $type; - $this->options->errorCorrectLevel = $eclevel; - $this->assertInstanceOf(QRCode::class, new QRCode($data, $this->output, $this->options)); - } - } - } - - /** - * @expectedException \chillerlan\QRCode\QRCodeException - * @expectedExceptionMessage No data given. - */ - public function testNoDataException(){ - $this->reflectionClass->newInstanceArgs(['', $this->output]); - } - - /** - * @expectedException \chillerlan\QRCode\QRCodeException - * @expectedExceptionMessage Invalid error correct level: 42 - */ - public function testErrorCorrectLevelException(){ - $this->options->errorCorrectLevel = 42; - $this->reflectionClass->newInstanceArgs(['foobar', $this->output, $this->options]); - } - - /** - * @expectedException \chillerlan\QRCode\QRCodeException - * @expectedExceptionMessage code length overflow. (261 > 72bit) - */ - public function testCodeLengthOverflowException(){ - $this->options->typeNumber = QRCode::TYPE_01; - $this->options->errorCorrectLevel = QRCode::ERROR_CORRECT_LEVEL_H; - - $method = $this->reflectionClass->getMethod('getRawData'); - $method->invoke($this->reflectionClass->newInstanceArgs(['ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', $this->output, $this->options])); - } - - public function testAuthenticatorExample(){ - $authenticator = new Authenticator; - - $data = $authenticator->getUri($authenticator->createSecret(), 'test', 'chillerlan.net'); - $qrcode = $this->reflectionClass->newInstanceArgs([$data, new QRImage(new QRImageOptions(['type' => QRCode::OUTPUT_IMAGE_GIF]))]); - - $this->markTestSkipped(print_r($qrcode->output(), true)); - } - } diff --git a/tests/QRDataGeneratorTest.php b/tests/QRDataGeneratorTest.php deleted file mode 100644 index 134ed823b..000000000 --- a/tests/QRDataGeneratorTest.php +++ /dev/null @@ -1,172 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeTest; - -use chillerlan\QRCode\Data\AlphaNum; -use chillerlan\QRCode\Data\QRDataInterface; -use chillerlan\QRCode\Output\QRString; -use chillerlan\QRCode\QRCode; -use chillerlan\QRCode\QRDataGenerator; -use chillerlan\QRCode\QROptions; -use PHPUnit\Framework\TestCase; -use ReflectionClass; -use ReflectionMethod; -use ReflectionProperty; - -class QRDataGeneratorTest extends TestCase{ - - /** - * @var \chillerlan\QRCode\QRDataGenerator - */ - protected $qrTest; - - /** - * @var \ReflectionClass - */ - protected $reflectionClass; - - - protected function setUp(){ - $this->reflectionClass = new ReflectionClass(QRDataGenerator::class); - - $this->qrTest = new QRDataGenerator('test', QRCode::TYPE_05, QRCode::ERROR_CORRECT_LEVEL_L); - } - - private function getMethod(string $method):ReflectionMethod { - $method = $this->reflectionClass->getMethod($method); - $method->setAccessible(true); - - return $method; - } - - private function getProperty(string $property):ReflectionProperty{ - $property = $this->reflectionClass->getProperty($property); - $property->setAccessible(true); - - return $property; - } - - public function stringDataProvider(){ - return [ - ['1234567890', QRCode::TYPE_01], - ['ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', QRCode::TYPE_03], - ['#\\', QRCode::TYPE_01], - ['่Œ—่ท', QRCode::TYPE_01], - ]; - } - - /** - * @dataProvider stringDataProvider - */ - public function testTypeAutoOverride($data, $type){ - $qrcode = $this->reflectionClass->newInstanceArgs([$data, $type, QRCode::ERROR_CORRECT_LEVEL_L]); - - $this->assertSame($type, $this->getProperty('typeNumber')->getValue($qrcode)); - } - - public function getMatrixDataProvider(){ - return [ - [QRCode::TYPE_01, 'foobar', 21], - [QRCode::TYPE_05, 'foobar', 37], - [QRCode::TYPE_10, 'foobar', 57], - [QRCode::TYPE_05, '1234567890', 37], - [QRCode::TYPE_10, '1234567890', 57], - [QRCode::TYPE_03, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 29], - [QRCode::TYPE_05, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 37], - [QRCode::TYPE_10, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 57], - [QRCode::TYPE_05, '่Œ—่ท', 37], - [QRCode::TYPE_10, '่Œ—่ท', 57], - ]; - } - - /** - * @dataProvider getMatrixDataProvider - */ - public function testInternalGetMatrix($type, $data, $pixelCount){ - $method = $this->getMethod('getMatrix'); - $property = $this->getProperty('pixelCount'); - - for($i = 0; $i <= 7; $i++){ - $qrcode = $this->reflectionClass->newInstanceArgs([$data, $type, QRCode::ERROR_CORRECT_LEVEL_L]); - $method->invokeArgs($qrcode, [false, $i]); - - $this->assertSame($pixelCount, $property->getValue($qrcode)); - } - } - - public function testIsNumber(){ - $this->assertTrue($this->qrTest->isNumber('1234567890')); - $this->assertFalse($this->qrTest->isNumber('abc')); - } - - public function testIsAlphaNum(){ - $this->assertTrue($this->qrTest->isAlphaNum('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:')); - $this->assertFalse($this->qrTest->isAlphaNum('#')); - } - - // http://stackoverflow.com/a/24755772 - public function testIsKanji(){ - $this->assertTrue($this->qrTest->isKanji('่Œ—่ท')); - $this->assertFalse($this->qrTest->isKanji('')); - $this->assertFalse($this->qrTest->isKanji('รƒรƒรƒ')); // non-kanji - $this->assertFalse($this->qrTest->isKanji('่ท')); // kanji forced into byte mode due to length - } - - // coverage - public function testGetBCHTypeNumber(){ - $this->assertSame(7973, $this->getMethod('getBCHTypeNumber')->invokeArgs($this->qrTest, [1])); - } - - /** - * @expectedException \chillerlan\QRCode\QRCodeException - * @expectedExceptionMessage $typeNumber: 1 / $errorCorrectLevel: 42 - */ - public function testGetRSBlocksException(){ - $this->getMethod('getRSBlocks')->invokeArgs($this->qrTest, [1, 42]); - } - - /** - * @expectedException \chillerlan\QRCode\QRCodeException - * @expectedExceptionMessage Invalid error correct level: 42 - */ - public function testGetMaxLengthECLevelException(){ - $this->getMethod('getMaxLength')->invokeArgs($this->qrTest, [QRCode::TYPE_01, QRDataInterface::MODE_BYTE, 42]); - } - - /** - * @expectedException \chillerlan\QRCode\QRCodeException - * @expectedExceptionMessage Invalid mode: 1337 - */ - public function testGetMaxLengthModeException(){ - $this->getMethod('getMaxLength')->invokeArgs($this->qrTest, [QRCode::TYPE_01, 1337, QRCode::ERROR_CORRECT_LEVEL_H]); - } - - public function getTypeNumberDataProvider(){ - return [ - [QRDataInterface::MODE_ALPHANUM, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 2], - [QRDataInterface::MODE_BYTE, '#\\', 1], - [QRDataInterface::MODE_KANJI, '่Œ—่ท', 1], - [QRDataInterface::MODE_NUMBER, '1234567890', 1], - ]; - } - - /** - * @dataProvider getTypeNumberDataProvider - */ - public function testGetTypeNumber($mode, $data, $expected){ - $i = QRDataGenerator::DATA_INTERFACES[$mode]; - - $this->assertSame($expected, $this->getMethod('getTypeNumber')->invokeArgs($this->qrTest, [new $i($data), $mode])); - } - -} diff --git a/tests/QRTestAbstract.php b/tests/QRTestAbstract.php new file mode 100644 index 000000000..a6bf95495 --- /dev/null +++ b/tests/QRTestAbstract.php @@ -0,0 +1,74 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest; + +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use ReflectionMethod; +use ReflectionProperty; + +abstract class QRTestAbstract extends TestCase{ + + /** + * @var \ReflectionClass + */ + protected $reflection; + + /** + * @var string + */ + protected $FQCN; + + protected function setUp(){ + $this->reflection = new ReflectionClass($this->FQCN); + } + + /** + * @param string $method + * + * @return \ReflectionMethod + */ + protected function getMethod(string $method):ReflectionMethod { + $method = $this->reflection->getMethod($method); + $method->setAccessible(true); + + return $method; + } + + /** + * @param string $property + * + * @return \ReflectionProperty + */ + protected function getProperty(string $property):ReflectionProperty{ + $property = $this->reflection->getProperty($property); + $property->setAccessible(true); + + return $property; + } + + /** + * @param $object + * @param string $property + * @param $value + * + * @return void + */ + protected function setProperty($object, string $property, $value){ + $property = $this->getProperty($property); + $property->setAccessible(true); + $property->setValue($object, $value); + } + + +}