diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a140293 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/test export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpcs.xml export-ignore +/phpunit.xml.dist export-ignore diff --git a/.gitignore b/.gitignore index c4fcf18..ff72e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,2 @@ -composer.lock -vendor -nbproject -.idea -.buildpath -.project -.DS_Store -.*.sw* -.*.un~ +/composer.lock +/vendor diff --git a/.travis.yml b/.travis.yml index 242163a..98faf89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,41 @@ +sudo: false + language: php -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - hhvm + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.local + - vendor + +matrix: + fast_finish: true + include: + - php: 7.1 + env: + - EXECUTE_CS_CHECK=true + - EXECUTE_TEST_COVERALLS=true + - PATH="$HOME/.local/bin:$PATH" + - php: nightly + allow_failures: + - php: nightly + +before_install: + - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi + - composer self-update + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls:2.0.0 ; fi install: - travis_retry composer install --no-interaction - composer info -i -script: vendor/bin/phpunit --bootstrap tests/bootstrap.php --configuration tests/phpunit.xml tests +script: + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then vendor/bin/phpunit --coverage-clover clover.xml ; fi + - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then vendor/bin/phpunit ; fi + - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then vendor/bin/phpcs ; fi + +after_script: + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then vendor/bin/coveralls ; fi + +notifications: + email: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3409ee6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 2.0.0 - 2018-04-25 + +### Added + +- [#25](https://github.com/Bacon/BaconQrCode/pull/25) allows for setting a more compact text output + +- CHANGELOG.md added (how meta) + +- Allows more complex shapes for modules + +- Allows setting a gradient for the foreground + +- Allows transparent backgrounds and alpha channel on all colors + +### Changed + +- Minimum PHP version changed to 7.1 + +- Imagick renderer now allows setting different output formats + +- New optimized SVG renderer + +### Deprecated + +- Nothing. + +### Removed + +- Legacy ZF module support removed + +### Fixed + +- Non-release files are excluded from composer packages diff --git a/LICENSE b/LICENSE index a72461a..d45a356 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013, Ben 'DASPRiD' Scholzen +Copyright (c) 2017, Ben Scholzen 'DASPRiD' All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Module.php b/Module.php deleted file mode 100644 index ad7798c..0000000 --- a/Module.php +++ /dev/null @@ -1,37 +0,0 @@ - array( - __DIR__ . '/autoload_classmap.php', - ), - 'Zend\Loader\StandardAutoloader' => array( - 'namespaces' => array( - __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, - ), - ), - ); - } -} diff --git a/README.md b/README.md index a836bd6..ba006c1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -QR Code generator -================= +# QR Code generator -Master: [![Build Status](https://api.travis-ci.org/Bacon/BaconQrCode.png?branch=master)](http://travis-ci.org/Bacon/BaconQrCode) +[![Build Status](https://api.travis-ci.org/Bacon/BaconQrCode.png?branch=master)](http://travis-ci.org/Bacon/BaconQrCode) +[![Coverage Status](https://coveralls.io/repos/github/Bacon/BaconQrCode/badge.svg?branch=master)](https://coveralls.io/github/Bacon/BaconQrCode?branch=master) +[![Latest Stable Version](https://poser.pugx.org/bacon/bacon-qr-code/v/stable)](https://packagist.org/packages/bacon/bacon-qr-code) +[![Total Downloads](https://poser.pugx.org/bacon/bacon-qr-code/downloads)](https://packagist.org/packages/bacon/bacon-qr-code) +[![License](https://poser.pugx.org/bacon/bacon-qr-code/license)](https://packagist.org/packages/bacon/bacon-qr-code) -Introduction ------------- + +## Introduction BaconQrCode is a port of QR code portion of the ZXing library. It currently only features the encoder part, but could later receive the decoder part as well. @@ -13,12 +16,24 @@ As the Reed Solomon codec implementation of the ZXing library performs quite slow in PHP, it was exchanged with the implementation by Phil Karn. -Example usage -------------- +## Example usage ```php -$renderer = new \BaconQrCode\Renderer\Image\Png(); -$renderer->setHeight(256); -$renderer->setWidth(256); -$writer = new \BaconQrCode\Writer($renderer); +use BaconQrCode\Renderer\ImageRenderer; +use BaconQrCode\Renderer\Image\ImagickImageBackEnd; +use BaconQrCode\Renderer\RendererStyle\RendererStyle; +use BaconQrCode\Writer; + +$renderer = new ImageRenderer( + new RendererStyle(400), + new ImagickImageBackEnd() +); +$writer = new Writer($renderer); $writer->writeFile('Hello World!', 'qrcode.png'); ``` + +## Available image renderer back ends +BaconQrCode comes with multiple back ends for rendering images. Currently included are the following: + +- `ImagickImageBackEnd`: renders raster images using the Imagick library +- `SvgImageBackEnd`: renders SVG files using XMLWriter +- `EpsImageBackEnd`: renders EPS files diff --git a/autoload_classmap.php b/autoload_classmap.php deleted file mode 100644 index 9fbeb35..0000000 --- a/autoload_classmap.php +++ /dev/null @@ -1,43 +0,0 @@ - __DIR__ . '/src/BaconQrCode/Common/AbstractEnum.php', - 'BaconQrCode\Common\BitArray' => __DIR__ . '/src/BaconQrCode/Common/BitArray.php', - 'BaconQrCode\Common\BitMatrix' => __DIR__ . '/src/BaconQrCode/Common/BitMatrix.php', - 'BaconQrCode\Common\BitUtils' => __DIR__ . '/src/BaconQrCode/Common/BitUtils.php', - 'BaconQrCode\Common\CharacterSetEci' => __DIR__ . '/src/BaconQrCode/Common/CharacterSetEci.php', - 'BaconQrCode\Common\EcBlock' => __DIR__ . '/src/BaconQrCode/Common/EcBlock.php', - 'BaconQrCode\Common\EcBlocks' => __DIR__ . '/src/BaconQrCode/Common/EcBlocks.php', - 'BaconQrCode\Common\ErrorCorrectionLevel' => __DIR__ . '/src/BaconQrCode/Common/ErrorCorrectionLevel.php', - 'BaconQrCode\Common\FormatInformation' => __DIR__ . '/src/BaconQrCode/Common/FormatInformation.php', - 'BaconQrCode\Common\Mode' => __DIR__ . '/src/BaconQrCode/Common/Mode.php', - 'BaconQrCode\Common\ReedSolomonCodec' => __DIR__ . '/src/BaconQrCode/Common/ReedSolomonCodec.php', - 'BaconQrCode\Common\Version' => __DIR__ . '/src/BaconQrCode/Common/Version.php', - 'BaconQrCode\Encoder\BlockPair' => __DIR__ . '/src/BaconQrCode/Encoder/BlockPair.php', - 'BaconQrCode\Encoder\ByteMatrix' => __DIR__ . '/src/BaconQrCode/Encoder/ByteMatrix.php', - 'BaconQrCode\Encoder\Encoder' => __DIR__ . '/src/BaconQrCode/Encoder/Encoder.php', - 'BaconQrCode\Encoder\MaskUtil' => __DIR__ . '/src/BaconQrCode/Encoder/MaskUtil.php', - 'BaconQrCode\Encoder\MatrixUtil' => __DIR__ . '/src/BaconQrCode/Encoder/MatrixUtil.php', - 'BaconQrCode\Encoder\QrCode' => __DIR__ . '/src/BaconQrCode/Encoder/QrCode.php', - 'BaconQrCode\Exception\ExceptionInterface' => __DIR__ . '/src/BaconQrCode/Exception/ExceptionInterface.php', - 'BaconQrCode\Exception\InvalidArgumentException' => __DIR__ . '/src/BaconQrCode/Exception/InvalidArgumentException.php', - 'BaconQrCode\Exception\OutOfBoundsException' => __DIR__ . '/src/BaconQrCode/Exception/OutOfBoundsException.php', - 'BaconQrCode\Exception\RuntimeException' => __DIR__ . '/src/BaconQrCode/Exception/RuntimeException.php', - 'BaconQrCode\Exception\UnexpectedValueException' => __DIR__ . '/src/BaconQrCode/Exception/UnexpectedValueException.php', - 'BaconQrCode\Exception\WriterException' => __DIR__ . '/src/BaconQrCode/Exception/WriterException.php', - 'BaconQrCode\Renderer\Color\Cmyk' => __DIR__ . '/src/BaconQrCode/Renderer/Color/Cmyk.php', - 'BaconQrCode\Renderer\Color\ColorInterface' => __DIR__ . '/src/BaconQrCode/Renderer/Color/ColorInterface.php', - 'BaconQrCode\Renderer\Color\Gray' => __DIR__ . '/src/BaconQrCode/Renderer/Color/Gray.php', - 'BaconQrCode\Renderer\Color\Rgb' => __DIR__ . '/src/BaconQrCode/Renderer/Color/Rgb.php', - 'BaconQrCode\Renderer\Image\AbstractRenderer' => __DIR__ . '/src/BaconQrCode/Renderer/Image/AbstractRenderer.php', - 'BaconQrCode\Renderer\Image\Decorator\DecoratorInterface' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Decorator/DecoratorInterface.php', - 'BaconQrCode\Renderer\Image\Decorator\FinderPattern' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Decorator/FinderPattern.php', - 'BaconQrCode\Renderer\Image\Eps' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Eps.php', - 'BaconQrCode\Renderer\Image\Png' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Png.php', - 'BaconQrCode\Renderer\Image\RendererInterface' => __DIR__ . '/src/BaconQrCode/Renderer/Image/RendererInterface.php', - 'BaconQrCode\Renderer\Image\Svg' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Svg.php', - 'BaconQrCode\Renderer\RendererInterface' => __DIR__ . '/src/BaconQrCode/Renderer/RendererInterface.php', - 'BaconQrCode\Renderer\Text\Plain' => __DIR__ . '/src/BaconQrCode/Renderer/Text/Plain.php', - 'BaconQrCode\Renderer\Text\Html' => __DIR__ . '/src/BaconQrCode/Renderer/Text/Html.php', - 'BaconQrCode\Writer' => __DIR__ . '/src/BaconQrCode/Writer.php', -); \ No newline at end of file diff --git a/autoload_function.php b/autoload_function.php deleted file mode 100644 index 9148da3..0000000 --- a/autoload_function.php +++ /dev/null @@ -1,12 +0,0 @@ - + + BaconQrCode standard + + + + + + + + + + + + + + + + + + + + + + + + + src + test + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..4d2f9cf --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + ./test + + + + + + src + + + diff --git a/src/BaconQrCode/Common/AbstractEnum.php b/src/BaconQrCode/Common/AbstractEnum.php deleted file mode 100644 index 9544338..0000000 --- a/src/BaconQrCode/Common/AbstractEnum.php +++ /dev/null @@ -1,115 +0,0 @@ -strict = $strict; - $this->change($initialValue); - } - - /** - * Changes the value of the enum. - * - * @param mixed $value - * @return void - */ - public function change($value) - { - if (!in_array($value, $this->getConstList(), $this->strict)) { - throw new Exception\UnexpectedValueException('Value not a const in enum ' . get_class($this)); - } - - $this->value = $value; - } - - /** - * Gets current value. - * - * @return mixed - */ - public function get() - { - return $this->value; - } - - /** - * Gets all constants (possible values) as an array. - * - * @param boolean $includeDefault - * @return array - */ - public function getConstList($includeDefault = true) - { - if ($this->constants === null) { - $reflection = new ReflectionClass($this); - $this->constants = $reflection->getConstants(); - } - - if ($includeDefault) { - return $this->constants; - } - - $constants = $this->constants; - unset($constants['__default']); - - return $constants; - } - - /** - * Gets the name of the enum. - * - * @return string - */ - public function __toString() - { - return array_search($this->value, $this->getConstList()); - } -} diff --git a/src/BaconQrCode/Common/CharacterSetEci.php b/src/BaconQrCode/Common/CharacterSetEci.php deleted file mode 100644 index 7766236..0000000 --- a/src/BaconQrCode/Common/CharacterSetEci.php +++ /dev/null @@ -1,134 +0,0 @@ - self::ISO8859_1, - 'ISO-8859-2' => self::ISO8859_2, - 'ISO-8859-3' => self::ISO8859_3, - 'ISO-8859-4' => self::ISO8859_4, - 'ISO-8859-5' => self::ISO8859_5, - 'ISO-8859-6' => self::ISO8859_6, - 'ISO-8859-7' => self::ISO8859_7, - 'ISO-8859-8' => self::ISO8859_8, - 'ISO-8859-9' => self::ISO8859_9, - 'ISO-8859-10' => self::ISO8859_10, - 'ISO-8859-11' => self::ISO8859_11, - 'ISO-8859-12' => self::ISO8859_12, - 'ISO-8859-13' => self::ISO8859_13, - 'ISO-8859-14' => self::ISO8859_14, - 'ISO-8859-15' => self::ISO8859_15, - 'ISO-8859-16' => self::ISO8859_16, - 'SHIFT-JIS' => self::SJIS, - 'WINDOWS-1250' => self::CP1250, - 'WINDOWS-1251' => self::CP1251, - 'WINDOWS-1252' => self::CP1252, - 'WINDOWS-1256' => self::CP1256, - 'UTF-16BE' => self::UNICODE_BIG_UNMARKED, - 'UTF-8' => self::UTF8, - 'ASCII' => self::ASCII, - 'GBK' => self::GB18030, - 'EUC-KR' => self::EUC_KR, - ); - - /** - * Additional possible values for character sets. - * - * @var array - */ - protected $additionalValues = array( - self::CP437 => 2, - self::ASCII => 170, - ); - - /** - * Gets character set ECI by value. - * - * @param string $name - * @return CharacterSetEci|null - */ - public static function getCharacterSetECIByValue($value) - { - if ($value < 0 || $value >= 900) { - throw new Exception\InvalidArgumentException('Value must be between 0 and 900'); - } - - if (false !== ($key = array_search($value, self::$additionalValues))) { - $value = $key; - } - - try { - return new self($value); - } catch (Exception\UnexpectedValueException $e) { - return null; - } - } - - /** - * Gets character set ECI by name. - * - * @param string $name - * @return CharacterSetEci|null - */ - public static function getCharacterSetECIByName($name) - { - $name = strtoupper($name); - - if (isset(self::$nameToEci[$name])) { - return new self(self::$nameToEci[$name]); - } - - return null; - } -} diff --git a/src/BaconQrCode/Common/EcBlock.php b/src/BaconQrCode/Common/EcBlock.php deleted file mode 100644 index cbcc2ba..0000000 --- a/src/BaconQrCode/Common/EcBlock.php +++ /dev/null @@ -1,65 +0,0 @@ -count = $count; - $this->dataCodewords = $dataCodewords; - } - - /** - * Returns how many times the block is used. - * - * @return integer - */ - public function getCount() - { - return $this->count; - } - - /** - * Returns the number of data codewords. - * - * @return integer - */ - public function getDataCodewords() - { - return $this->dataCodewords; - } -} diff --git a/src/BaconQrCode/Common/EcBlocks.php b/src/BaconQrCode/Common/EcBlocks.php deleted file mode 100644 index 87cef5d..0000000 --- a/src/BaconQrCode/Common/EcBlocks.php +++ /dev/null @@ -1,101 +0,0 @@ -ecCodewordsPerBlock = $ecCodewordsPerBlock; - - $this->ecBlocks = new SplFixedArray($ecb2 === null ? 1 : 2); - $this->ecBlocks[0] = $ecb1; - - if ($ecb2 !== null) { - $this->ecBlocks[1] = $ecb2; - } - } - - /** - * Gets the number of EC codewords per block. - * - * @return integer - */ - public function getEcCodewordsPerBlock() - { - return $this->ecCodewordsPerBlock; - } - - /** - * Gets the total number of EC block appearances. - * - * @return integer - */ - public function getNumBlocks() - { - $total = 0; - - foreach ($this->ecBlocks as $ecBlock) { - $total += $ecBlock->getCount(); - } - - return $total; - } - - /** - * Gets the total count of EC codewords. - * - * @return integer - */ - public function getTotalEcCodewords() - { - return $this->ecCodewordsPerBlock * $this->getNumBlocks(); - } - - /** - * Gets the EC blocks included in this collection. - * - * @return SplFixedArray - */ - public function getEcBlocks() - { - return $this->ecBlocks; - } -} diff --git a/src/BaconQrCode/Common/ErrorCorrectionLevel.php b/src/BaconQrCode/Common/ErrorCorrectionLevel.php deleted file mode 100644 index bd0a60a..0000000 --- a/src/BaconQrCode/Common/ErrorCorrectionLevel.php +++ /dev/null @@ -1,62 +0,0 @@ -value) { - case self::L: - return 0; - break; - - case self::M: - return 1; - break; - - case self::Q: - return 2; - break; - - case self::H: - return 3; - break; - } - } -} diff --git a/src/BaconQrCode/Common/FormatInformation.php b/src/BaconQrCode/Common/FormatInformation.php deleted file mode 100644 index 5ec9ffd..0000000 --- a/src/BaconQrCode/Common/FormatInformation.php +++ /dev/null @@ -1,236 +0,0 @@ -ecLevel = new ErrorCorrectionLevel(($formatInfo >> 3) & 0x3); - $this->dataMask = $formatInfo & 0x7; - } - - /** - * Checks how many bits are different between two integers. - * - * @param integer $a - * @param integer $b - * @return integer - */ - public static function numBitsDiffering($a, $b) - { - $a ^= $b; - - return ( - self::$bitsSetInHalfByte[$a & 0xf] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 4) & 0xf)] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 8) & 0xf)] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 12) & 0xf)] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 16) & 0xf)] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 20) & 0xf)] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 24) & 0xf)] - + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 28) & 0xf)] - ); - } - - /** - * Decodes format information. - * - * @param integer $maskedFormatInfo1 - * @param integer $maskedFormatInfo2 - * @return FormatInformation|null - */ - public static function decodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2) - { - $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); - - if ($formatInfo !== null) { - return $formatInfo; - } - - // Should return null, but, some QR codes apparently do not mask this - // info. Try again by actually masking the pattern first. - return self::doDecodeFormatInformation( - $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, - $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR - ); - } - - /** - * Internal method for decoding format information. - * - * @param integer $maskedFormatInfo1 - * @param integer $maskedFormatInfo2 - * @return FormatInformation|null - */ - protected static function doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2) - { - $bestDifference = PHP_INT_MAX; - $bestFormatInfo = 0; - - foreach (self::$formatInfoDecodeLookup as $decodeInfo) { - $targetInfo = $decodeInfo[0]; - - if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { - // Found an exact match - return new self($decodeInfo[1]); - } - - $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); - - if ($bitsDifference < $bestDifference) { - $bestFormatInfo = $decodeInfo[1]; - $bestDifference = $bitsDifference; - } - - if ($maskedFormatInfo1 !== $maskedFormatInfo2) { - // Also try the other option - $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); - - if ($bitsDifference < $bestDifference) { - $bestFormatInfo = $decodeInfo[1]; - $bestDifference = $bitsDifference; - } - } - } - - // Hamming distance of the 32 masked codes is 7, by construction, so - // <= 3 bits differing means we found a match. - if ($bestDifference <= 3) { - return new self($bestFormatInfo); - } - - return null; - } - - /** - * Gets the error correction level. - * - * @return ErrorCorrectionLevel - */ - public function getErrorCorrectionLevel() - { - return $this->ecLevel; - } - - /** - * Gets the data mask. - * - * @return integer - */ - public function getDataMask() - { - return $this->dataMask; - } - - /** - * Hashes the code of the EC level. - * - * @return integer - */ - public function hashCode() - { - return ($this->ecLevel->get() << 3) | $this->dataMask; - } - - /** - * Verifies if this instance equals another one. - * - * @param mixed $other - * @return boolean - */ - public function equals($other) { - if (!$other instanceof self) { - return false; - } - - return ( - $this->ecLevel->get() === $other->getErrorCorrectionLevel()->get() - && $this->dataMask === $other->getDataMask() - ); - } -} diff --git a/src/BaconQrCode/Common/Mode.php b/src/BaconQrCode/Common/Mode.php deleted file mode 100644 index 8faf344..0000000 --- a/src/BaconQrCode/Common/Mode.php +++ /dev/null @@ -1,70 +0,0 @@ - array(0, 0, 0), - self::NUMERIC => array(10, 12, 14), - self::ALPHANUMERIC => array(9, 11, 13), - self::STRUCTURED_APPEND => array(0, 0, 0), - self::BYTE => array(8, 16, 16), - self::ECI => array(0, 0, 0), - self::KANJI => array(8, 10, 12), - self::FNC1_FIRST_POSITION => array(0, 0, 0), - self::FNC1_SECOND_POSITION => array(0, 0, 0), - self::HANZI => array(8, 10, 12), - ); - - /** - * Gets the number of bits used in a specific QR code version. - * - * @param Version $version - * @return integer - */ - public function getCharacterCountBits(Version $version) - { - $number = $version->getVersionNumber(); - - if ($number <= 9) { - $offset = 0; - } elseif ($number <= 26) { - $offset = 1; - } else { - $offset = 2; - } - - return self::$characterCountBitsForVersions[$this->value][$offset]; - } -} diff --git a/src/BaconQrCode/Common/Version.php b/src/BaconQrCode/Common/Version.php deleted file mode 100644 index d698639..0000000 --- a/src/BaconQrCode/Common/Version.php +++ /dev/null @@ -1,687 +0,0 @@ -versionNumber = $versionNumber; - $this->alignmentPatternCenters = $alignmentPatternCenters; - $this->errorCorrectionBlocks = $ecBlocks; - - $totalCodewords = 0; - $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock(); - - foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) { - $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords); - } - - $this->totalCodewords = $totalCodewords; - } - - /** - * Gets the version number. - * - * @return integer - */ - public function getVersionNumber() - { - return $this->versionNumber; - } - - /** - * Gets the alignment pattern centers. - * - * @return SplFixedArray - */ - public function getAlignmentPatternCenters() - { - return $this->alignmentPatternCenters; - } - - /** - * Gets the total number of codewords. - * - * @return integer - */ - public function getTotalCodewords() - { - return $this->totalCodewords; - } - - /** - * Gets the dimension for the current version. - * - * @return integer - */ - public function getDimensionForVersion() - { - return 17 + 4 * $this->versionNumber; - } - - /** - * Gets the number of EC blocks for a specific EC level. - * - * @param ErrorCorrectionLevel $ecLevel - * @return integer - */ - public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) - { - return $this->errorCorrectionBlocks[$ecLevel->getOrdinal()]; - } - - /** - * Gets a provisional version number for a specific dimension. - * - * @param integer $dimension - * @return Version - * @throws Exception\InvalidArgumentException - */ - public static function getProvisionalVersionForDimension($dimension) - { - if ($dimension % 4 !== 1) { - throw new Exception\InvalidArgumentException('Dimension is not 1 mod 4'); - } - - return self::getVersionForNumber(($dimension - 17) >> 2); - } - - /** - * Gets a version instance for a specific version number. - * - * @param integer $versionNumber - * @return Version - * @throws Exception\InvalidArgumentException - */ - public static function getVersionForNumber($versionNumber) - { - if ($versionNumber < 1 || $versionNumber > 40) { - throw new Exception\InvalidArgumentException('Version number must be between 1 and 40'); - } - - if (!isset(self::$versions[$versionNumber])) { - self::buildVersion($versionNumber); - } - - return self::$versions[$versionNumber - 1]; - } - - /** - * Decodes version information from an integer and returns the version. - * - * @param integer $versionBits - * @return Version|null - */ - public static function decodeVersionInformation($versionBits) - { - $bestDifference = PHP_INT_MAX; - $bestVersion = 0; - - foreach (self::$versionDecodeInfo as $i => $targetVersion) { - if ($targetVersion === $versionBits) { - return self::getVersionForNumber($i + 7); - } - - $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion); - - if ($bitsDifference < $bestDifference) { - $bestVersion = $i + 7; - $bestDifference = $bitsDifference; - } - } - - if ($bestDifference <= 3) { - return self::getVersionForNumber($bestVersion); - } - - return null; - } - - /** - * Builds the function pattern for the current version. - * - * @return BitMatrix - */ - public function buildFunctionPattern() - { - $dimension = $this->getDimensionForVersion(); - $bitMatrix = new BitMatrix($dimension); - - // Top left finder pattern + separator + format - $bitMatrix->setRegion(0, 0, 9, 9); - // Top right finder pattern + separator + format - $bitMatrix->setRegion($dimension - 8, 0, 8, 9); - // Bottom left finder pattern + separator + format - $bitMatrix->setRegion(0, $dimension - 8, 9, 8); - - // Alignment patterns - $max = count($this->alignmentPatternCenters); - - for ($x = 0; $x < $max; $x++) { - $i = $this->alignmentPatternCenters[$x] - 2; - - for ($y = 0; $y < $max; $y++) { - if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) { - // No alignment patterns near the three finder paterns - continue; - } - - $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5); - } - } - - // Vertical timing pattern - $bitMatrix->setRegion(6, 9, 1, $dimension - 17); - // Horizontal timing pattern - $bitMatrix->setRegion(9, 6, $dimension - 17, 1); - - if ($this->versionNumber > 6) { - // Version info, top right - $bitMatrix->setRegion($dimension - 11, 0, 3, 6); - // Version info, bottom left - $bitMatrix->setRegion(0, $dimension - 11, 6, 3); - } - - return $bitMatrix; - } - - /** - * Returns a string representation for the version. - * - * @return string - */ - public function __toString() - { - return (string) $this->versionNumber; - } - - /** - * Build and cache a specific version. - * - * See ISO 18004:2006 6.5.1 Table 9. - * - * @param integer $versionNumber - * @return void - */ - protected static function buildVersion($versionNumber) - { - switch ($versionNumber) { - case 1: - $patterns = array(); - $ecBlocks = array( - new EcBlocks(7, new EcBlock(1, 19)), - new EcBlocks(10, new EcBlock(1, 16)), - new EcBlocks(13, new EcBlock(1, 13)), - new EcBlocks(17, new EcBlock(1, 9)), - ); - break; - - case 2: - $patterns = array(6, 18); - $ecBlocks = array( - new EcBlocks(10, new EcBlock(1, 34)), - new EcBlocks(16, new EcBlock(1, 28)), - new EcBlocks(22, new EcBlock(1, 22)), - new EcBlocks(28, new EcBlock(1, 16)), - ); - break; - - case 3: - $patterns = array(6, 22); - $ecBlocks = array( - new EcBlocks(15, new EcBlock(1, 55)), - new EcBlocks(26, new EcBlock(1, 44)), - new EcBlocks(18, new EcBlock(2, 17)), - new EcBlocks(22, new EcBlock(2, 13)), - ); - break; - - case 4: - $patterns = array(6, 26); - $ecBlocks = array( - new EcBlocks(20, new EcBlock(1, 80)), - new EcBlocks(18, new EcBlock(2, 32)), - new EcBlocks(26, new EcBlock(3, 24)), - new EcBlocks(16, new EcBlock(4, 9)), - ); - break; - - case 5: - $patterns = array(6, 30); - $ecBlocks = array( - new EcBlocks(26, new EcBlock(1, 108)), - new EcBlocks(24, new EcBlock(2, 43)), - new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)), - new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12)), - ); - break; - - case 6: - $patterns = array(6, 34); - $ecBlocks = array( - new EcBlocks(18, new EcBlock(2, 68)), - new EcBlocks(16, new EcBlock(4, 27)), - new EcBlocks(24, new EcBlock(4, 19)), - new EcBlocks(28, new EcBlock(4, 15)), - ); - break; - - case 7: - $patterns = array(6, 22, 38); - $ecBlocks = array( - new EcBlocks(20, new EcBlock(2, 78)), - new EcBlocks(18, new EcBlock(4, 31)), - new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)), - new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14)), - ); - break; - - case 8: - $patterns = array(6, 24, 42); - $ecBlocks = array( - new EcBlocks(24, new EcBlock(2, 97)), - new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)), - new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)), - new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15)), - ); - break; - - case 9: - $patterns = array(6, 26, 46); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(2, 116)), - new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)), - new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)), - new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13)), - ); - break; - - case 10: - $patterns = array(6, 28, 50); - $ecBlocks = array( - new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)), - new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)), - new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)), - new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16)), - ); - break; - - case 11: - $patterns = array(6, 30, 54); - $ecBlocks = array( - new EcBlocks(20, new EcBlock(4, 81)), - new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)), - new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)), - new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13)), - ); - break; - - case 12: - $patterns = array(6, 32, 58); - $ecBlocks = array( - new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)), - new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)), - new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)), - new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15)), - ); - break; - - case 13: - $patterns = array(6, 34, 62); - $ecBlocks = array( - new EcBlocks(26, new EcBlock(4, 107)), - new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)), - new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)), - new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12)), - ); - break; - - case 14: - $patterns = array(6, 26, 46, 66); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)), - new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)), - new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)), - new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13)), - ); - break; - - case 15: - $patterns = array(6, 26, 48, 70); - $ecBlocks = array( - new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)), - new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)), - new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)), - new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13)), - ); - break; - - case 16: - $patterns = array(6, 26, 50, 74); - $ecBlocks = array( - new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)), - new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)), - new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)), - new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16)), - ); - break; - - case 17: - $patterns = array(6, 30, 54, 78); - $ecBlocks = array( - new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)), - new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)), - new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)), - new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15)), - ); - break; - - case 18: - $patterns = array(6, 30, 56, 82); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)), - new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)), - new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)), - new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15)), - ); - break; - - case 19: - $patterns = array(6, 30, 58, 86); - $ecBlocks = array( - new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)), - new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)), - new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)), - new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14)), - ); - break; - - case 20: - $patterns = array(6, 34, 62, 90); - $ecBlocks = array( - new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)), - new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)), - new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)), - new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16)), - ); - break; - - case 21: - $patterns = array(6, 28, 50, 72, 94); - $ecBlocks = array( - new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)), - new EcBlocks(26, new EcBlock(17, 42)), - new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)), - new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17)), - ); - break; - - case 22: - $patterns = array(6, 26, 50, 74, 98); - $ecBlocks = array( - new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)), - new EcBlocks(28, new EcBlock(17, 46)), - new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)), - new EcBlocks(24, new EcBlock(34, 13)), - ); - break; - - case 23: - $patterns = array(6, 30, 54, 78, 102); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)), - new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)), - new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)), - new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16)), - ); - break; - - case 24: - $patterns = array(6, 28, 54, 80, 106); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)), - new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)), - new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)), - new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17)), - ); - break; - - case 25: - $patterns = array(6, 32, 58, 84, 110); - $ecBlocks = array( - new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)), - new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)), - new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)), - new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16)), - ); - break; - - case 26: - $patterns = array(6, 30, 58, 86, 114); - $ecBlocks = array( - new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)), - new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)), - new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)), - new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17)), - ); - break; - - case 27: - $patterns = array(6, 34, 62, 90, 118); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)), - new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)), - new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)), - new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16)), - ); - break; - - case 28: - $patterns = array(6, 26, 50, 74, 98, 122); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)), - new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)), - new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)), - new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16)), - ); - break; - - case 29: - $patterns = array(6, 30, 54, 78, 102, 126); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)), - new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)), - new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)), - new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16)), - ); - break; - - case 30: - $patterns = array(6, 26, 52, 78, 104, 130); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)), - new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)), - new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)), - new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16)), - ); - break; - - case 31: - $patterns = array(6, 30, 56, 82, 108, 134); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)), - new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)), - new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)), - new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16)), - ); - break; - - case 32: - $patterns = array(6, 34, 60, 86, 112, 138); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(17, 115)), - new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)), - new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)), - new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16)), - ); - break; - - case 33: - $patterns = array(6, 30, 58, 86, 114, 142); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)), - new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)), - new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)), - new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16)), - ); - break; - - case 34: - $patterns = array(6, 34, 62, 90, 118, 146); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)), - new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)), - new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)), - new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17)), - ); - break; - - case 35: - $patterns = array(6, 30, 54, 78, 102, 126, 150); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)), - new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)), - new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)), - new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16)), - ); - break; - - case 36: - $patterns = array(6, 24, 50, 76, 102, 128, 154); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)), - new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)), - new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)), - new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16)), - ); - break; - - case 37: - $patterns = array(6, 28, 54, 80, 106, 132, 158); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)), - new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)), - new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)), - new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16)), - ); - break; - - case 38: - $patterns = array(6, 32, 58, 84, 110, 136, 162); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)), - new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)), - new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)), - new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16)), - ); - break; - - case 39: - $patterns = array(6, 26, 54, 82, 110, 138, 166); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)), - new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)), - new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)), - new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16)), - ); - break; - - case 40: - $patterns = array(6, 30, 58, 86, 114, 142, 170); - $ecBlocks = array( - new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)), - new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)), - new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)), - new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16)), - ); - break; - } - - self::$versions[$versionNumber - 1] = new self( - $versionNumber, - SplFixedArray::fromArray($patterns, false), - SplFixedArray::fromArray($ecBlocks, false) - ); - } -} diff --git a/src/BaconQrCode/Encoder/MatrixUtil.php b/src/BaconQrCode/Encoder/MatrixUtil.php deleted file mode 100644 index 8327381..0000000 --- a/src/BaconQrCode/Encoder/MatrixUtil.php +++ /dev/null @@ -1,580 +0,0 @@ -clear(-1); - } - - /** - * Builds a complete matrix. - * - * @param BitArray $dataBits - * @param ErrorCorrectionLevel $level - * @param Version $version - * @param integer $maskPattern - * @param ByteMatrix $matrix - * @return void - */ - public static function buildMatrix( - BitArray $dataBits, - ErrorCorrectionLevel $level, - Version $version, - $maskPattern, - ByteMatrix $matrix - ) { - self::clearMatrix($matrix); - self::embedBasicPatterns($version, $matrix); - self::embedTypeInfo($level, $maskPattern, $matrix); - self::maybeEmbedVersionInfo($version, $matrix); - self::embedDataBits($dataBits, $maskPattern, $matrix); - } - - /** - * Embeds type information into a matrix. - * - * @param ErrorCorrectionLevel $level - * @param integer $maskPattern - * @param ByteMatrix $matrix - * @return void - */ - protected static function embedTypeInfo(ErrorCorrectionLevel $level, $maskPattern, ByteMatrix $matrix) - { - $typeInfoBits = new BitArray(); - self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits); - - $typeInfoBitsSize = $typeInfoBits->getSize(); - - for ($i = 0; $i < $typeInfoBitsSize; $i++) { - $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i); - - $x1 = self::$typeInfoCoordinates[$i][0]; - $y1 = self::$typeInfoCoordinates[$i][1]; - - $matrix->set($x1, $y1, $bit); - - if ($i < 8) { - $x2 = $matrix->getWidth() - $i - 1; - $y2 = 8; - } else { - $x2 = 8; - $y2 = $matrix->getHeight() - 7 + ($i - 8); - } - - $matrix->set($x2, $y2, $bit); - } - } - - /** - * Generates type information bits and appends them to a bit array. - * - * @param ErrorCorrectionLevel $level - * @param integer $maskPattern - * @param BitArray $bits - * @return void - * @throws Exception\RuntimeException - */ - protected static function makeTypeInfoBits(ErrorCorrectionLevel $level, $maskPattern, BitArray $bits) - { - $typeInfo = ($level->get() << 3) | $maskPattern; - $bits->appendBits($typeInfo, 5); - - $bchCode = self::calculateBchCode($typeInfo, self::$typeInfoPoly); - $bits->appendBits($bchCode, 10); - - $maskBits = new BitArray(); - $maskBits->appendBits(self::$typeInfoMaskPattern, 15); - $bits->xorBits($maskBits); - - if ($bits->getSize() !== 15) { - throw new Exception\RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); - } - } - - /** - * Embeds version information if required. - * - * @param Version $version - * @param ByteMatrix $matrix - * @return void - */ - protected static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) - { - if ($version->getVersionNumber() < 7) { - return; - } - - $versionInfoBits = new BitArray(); - self::makeVersionInfoBits($version, $versionInfoBits); - - $bitIndex = 6 * 3 - 1; - - for ($i = 0; $i < 6; $i++) { - for ($j = 0; $j < 3; $j++) { - $bit = $versionInfoBits->get($bitIndex); - $bitIndex--; - - $matrix->set($i, $matrix->getHeight() - 11 + $j, $bit); - $matrix->set($matrix->getHeight() - 11 + $j, $i, $bit); - } - } - } - - /** - * Generates version information bits and appends them to a bit array. - * - * @param Version $version - * @param BitArray $bits - * @return void - * @throws Exception\RuntimeException - */ - protected static function makeVersionInfoBits(Version $version, BitArray $bits) - { - $bits->appendBits($version->getVersionNumber(), 6); - - $bchCode = self::calculateBchCode($version->getVersionNumber(), self::$versionInfoPoly); - $bits->appendBits($bchCode, 12); - - if ($bits->getSize() !== 18) { - throw new Exception\RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); - } - } - - /** - * Calculates the BHC code for a value and a polynomial. - * - * @param integer $value - * @param integer $poly - * @return integer - */ - protected static function calculateBchCode($value, $poly) - { - $msbSetInPoly = self::findMsbSet($poly); - $value <<= $msbSetInPoly - 1; - - while (self::findMsbSet($value) >= $msbSetInPoly) { - $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly); - } - - return $value; - } - - /** - * Finds and MSB set. - * - * @param integer $value - * @return integer - */ - protected static function findMsbSet($value) - { - $numDigits = 0; - - while ($value !== 0) { - $value >>= 1; - $numDigits++; - } - - return $numDigits; - } - - /** - * Embeds basic patterns into a matrix. - * - * @param Version $version - * @param ByteMatrix $matrix - * @return void - */ - protected static function embedBasicPatterns(Version $version, ByteMatrix $matrix) - { - self::embedPositionDetectionPatternsAndSeparators($matrix); - self::embedDarkDotAtLeftBottomCorner($matrix); - self::maybeEmbedPositionAdjustmentPatterns($version, $matrix); - self::embedTimingPatterns($matrix); - } - - /** - * Embeds position detection patterns and separators into a byte matrix. - * - * @param ByteMatrix $matrix - * @return void - */ - protected static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) - { - $pdpWidth = count(self::$positionDetectionPattern[0]); - - self::embedPositionDetectionPattern(0, 0, $matrix); - self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); - self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); - - $hspWidth = 8; - - self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix); - self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix); - self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix); - - $vspSize = 7; - - self::embedVerticalSeparationPattern($vspSize, 0, $matrix); - self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix); - self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix); - } - - /** - * Embeds a single position detection pattern into a byte matrix. - * - * @param integer $xStart - * @param integer $yStart - * @param ByteMatrix $matrix - * @return void - */ - protected static function embedPositionDetectionPattern($xStart, $yStart, ByteMatrix $matrix) - { - for ($y = 0; $y < 7; $y++) { - for ($x = 0; $x < 7; $x++) { - $matrix->set($xStart + $x, $yStart + $y, self::$positionDetectionPattern[$y][$x]); - } - } - } - - /** - * Embeds a single horizontal separation pattern. - * - * @param integer $xStart - * @param integer $yStart - * @param ByteMatrix $matrix - * @return void - * @throws Exception\RuntimeException - */ - protected static function embedHorizontalSeparationPattern($xStart, $yStart, ByteMatrix $matrix) - { - for ($x = 0; $x < 8; $x++) { - if ($matrix->get($xStart + $x, $yStart) !== -1) { - throw new Exception\RuntimeException('Byte already set'); - } - - $matrix->set($xStart + $x, $yStart, 0); - } - } - - /** - * Embeds a single vertical separation pattern. - * - * @param integer $xStart - * @param integer $yStart - * @param ByteMatrix $matrix - * @return void - * @throws Exception\RuntimeException - */ - protected static function embedVerticalSeparationPattern($xStart, $yStart, ByteMatrix $matrix) - { - for ($y = 0; $y < 7; $y++) { - if ($matrix->get($xStart, $yStart + $y) !== -1) { - throw new Exception\RuntimeException('Byte already set'); - } - - $matrix->set($xStart, $yStart + $y, 0); - } - } - - /** - * Embeds a dot at the left bottom corner. - * - * @param ByteMatrix $matrix - * @return void - * @throws Exception\RuntimeException - */ - protected static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) - { - if ($matrix->get(8, $matrix->getHeight() - 8) === 0) { - throw new Exception\RuntimeException('Byte already set to 0'); - } - - $matrix->set(8, $matrix->getHeight() - 8, 1); - } - - /** - * Embeds position adjustment patterns if required. - * - * @param Version $version - * @param ByteMatrix $matrix - * @return void - */ - protected static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) - { - if ($version->getVersionNumber() < 2) { - return; - } - - $index = $version->getVersionNumber() - 1; - - $coordinates = self::$positionAdjustmentPatternCoordinateTable[$index]; - $numCoordinates = count($coordinates); - - for ($i = 0; $i < $numCoordinates; $i++) { - for ($j = 0; $j < $numCoordinates; $j++) { - $y = $coordinates[$i]; - $x = $coordinates[$j]; - - if ($x === null || $y === null) { - continue; - } - - if ($matrix->get($x, $y) === -1) { - self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix); - } - } - } - } - - /** - * Embeds a single position adjustment pattern. - * - * @param integer $xStart - * @param intger $yStart - * @param ByteMatrix $matrix - * @return void - */ - protected static function embedPositionAdjustmentPattern($xStart, $yStart, ByteMatrix $matrix) - { - for ($y = 0; $y < 5; $y++) { - for ($x = 0; $x < 5; $x++) { - $matrix->set($xStart + $x, $yStart + $y, self::$positionAdjustmentPattern[$y][$x]); - } - } - } - - /** - * Embeds timing patterns into a matrix. - * - * @param ByteMatrix $matrix - * @return void - */ - protected static function embedTimingPatterns(ByteMatrix $matrix) - { - $matrixWidth = $matrix->getWidth(); - - for ($i = 8; $i < $matrixWidth - 8; $i++) { - $bit = ($i + 1) % 2; - - if ($matrix->get($i, 6) === -1) { - $matrix->set($i, 6, $bit); - } - - if ($matrix->get(6, $i) === -1) { - $matrix->set(6, $i, $bit); - } - } - } - - /** - * Embeds "dataBits" using "getMaskPattern". - * - * For debugging purposes, it skips masking process if "getMaskPattern" is - * -1. See 8.7 of JISX0510:2004 (p.38) for how to embed data bits. - * - * @param BitArray $dataBits - * @param integer $maskPattern - * @param ByteMatrix $matrix - * @return void - * @throws Exception\WriterException - */ - protected static function embedDataBits(BitArray $dataBits, $maskPattern, ByteMatrix $matrix) - { - $bitIndex = 0; - $direction = -1; - - // Start from the right bottom cell. - $x = $matrix->getWidth() - 1; - $y = $matrix->getHeight() - 1; - - while ($x > 0) { - // Skip vertical timing pattern. - if ($x === 6) { - $x--; - } - - while ($y >= 0 && $y < $matrix->getHeight()) { - for ($i = 0; $i < 2; $i++) { - $xx = $x - $i; - - // Skip the cell if it's not empty. - if ($matrix->get($xx, $y) !== -1) { - continue; - } - - if ($bitIndex < $dataBits->getSize()) { - $bit = $dataBits->get($bitIndex); - $bitIndex++; - } else { - // Padding bit. If there is no bit left, we'll fill the - // left cells with 0, as described in 8.4.9 of - // JISX0510:2004 (p. 24). - $bit = false; - } - - // Skip masking if maskPattern is -1. - if ($maskPattern !== -1 && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) { - $bit = !$bit; - } - - $matrix->set($xx, $y, $bit); - } - - $y += $direction; - } - - $direction = -$direction; - $y += $direction; - $x -= 2; - } - - // All bits should be consumed - if ($bitIndex !== $dataBits->getSize()) { - throw new Exception\WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')'); - } - } -} diff --git a/src/BaconQrCode/Encoder/QrCode.php b/src/BaconQrCode/Encoder/QrCode.php deleted file mode 100644 index 07e1c38..0000000 --- a/src/BaconQrCode/Encoder/QrCode.php +++ /dev/null @@ -1,201 +0,0 @@ -mode; - } - - /** - * Sets the mode. - * - * @param Mode $mode - * @return void - */ - public function setMode(Mode $mode) - { - $this->mode = $mode; - } - - /** - * Gets the EC level. - * - * @return ErrorCorrectionLevel - */ - public function getErrorCorrectionLevel() - { - return $this->errorCorrectionLevel; - } - - /** - * Sets the EC level. - * - * @param ErrorCorrectionLevel $errorCorrectionLevel - * @return void - */ - public function setErrorCorrectionLevel(ErrorCorrectionLevel $errorCorrectionLevel) - { - $this->errorCorrectionLevel = $errorCorrectionLevel; - } - - /** - * Gets the version. - * - * @return Version - */ - public function getVersion() - { - return $this->version; - } - - /** - * Sets the version. - * - * @param Version $version - * @return void - */ - public function setVersion(Version $version) - { - $this->version = $version; - } - - /** - * Gets the mask pattern. - * - * @return integer - */ - public function getMaskPattern() - { - return $this->maskPattern; - } - - /** - * Sets the mask pattern. - * - * @param integer $maskPattern - * @return void - */ - public function setMaskPattern($maskPattern) - { - $this->maskPattern = $maskPattern; - } - - /** - * Gets the matrix. - * - * @return ByteMatrix - */ - public function getMatrix() - { - return $this->matrix; - } - - /** - * Sets the matrix. - * - * @param ByteMatrix $matrix - * @return void - */ - public function setMatrix(ByteMatrix $matrix) - { - $this->matrix = $matrix; - } - - /** - * Validates whether a mask pattern is valid. - * - * @param integer $maskPattern - * @return boolean - */ - public static function isValidMaskPattern($maskPattern) - { - return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS; - } - - /** - * Returns a string representation of the QR code. - * - * @return string - */ - public function __toString() - { - $result = "<<\n" - . " mode: " . $this->mode . "\n" - . " ecLevel: " . $this->errorCorrectionLevel . "\n" - . " version: " . $this->version . "\n" - . " maskPattern: " . $this->maskPattern . "\n"; - - if ($this->matrix === null) { - $result .= " matrix: null\n"; - } else { - $result .= " matrix:\n"; - $result .= $this->matrix; - } - - $result .= ">>\n"; - - return $result; - } -} diff --git a/src/BaconQrCode/Exception/ExceptionInterface.php b/src/BaconQrCode/Exception/ExceptionInterface.php deleted file mode 100644 index 5c58fc5..0000000 --- a/src/BaconQrCode/Exception/ExceptionInterface.php +++ /dev/null @@ -1,14 +0,0 @@ - 100) { - throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100'); - } - - if ($magenta < 0 || $magenta > 100) { - throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100'); - } - - if ($yellow < 0 || $yellow > 100) { - throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100'); - } - - if ($black < 0 || $black > 100) { - throw new Exception\InvalidArgumentException('Black must be between 0 and 100'); - } - - $this->cyan = (int) $cyan; - $this->magenta = (int) $magenta; - $this->yellow = (int) $yellow; - $this->black = (int) $black; - } - - /** - * Returns the cyan value. - * - * @return integer - */ - public function getCyan() - { - return $this->cyan; - } - - /** - * Returns the magenta value. - * - * @return integer - */ - public function getMagenta() - { - return $this->magenta; - } - - /** - * Returns the yellow value. - * - * @return integer - */ - public function getYellow() - { - return $this->yellow; - } - - /** - * Returns the black value. - * - * @return integer - */ - public function getBlack() - { - return $this->black; - } - - /** - * toRgb(): defined by ColorInterface. - * - * @see ColorInterface::toRgb() - * @return Rgb - */ - public function toRgb() - { - $k = $this->black / 100; - $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100; - $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100; - $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100; - - return new Rgb( - -$c * 255 + 255, - -$m * 255 + 255, - -$y * 255 + 255 - ); - } - - /** - * toCmyk(): defined by ColorInterface. - * - * @see ColorInterface::toCmyk() - * @return Cmyk - */ - public function toCmyk() - { - return $this; - } - - /** - * toGray(): defined by ColorInterface. - * - * @see ColorInterface::toGray() - * @return Gray - */ - public function toGray() - { - return $this->toRgb()->toGray(); - } -} \ No newline at end of file diff --git a/src/BaconQrCode/Renderer/Color/ColorInterface.php b/src/BaconQrCode/Renderer/Color/ColorInterface.php deleted file mode 100644 index 747accc..0000000 --- a/src/BaconQrCode/Renderer/Color/ColorInterface.php +++ /dev/null @@ -1,37 +0,0 @@ - 100) { - throw new Exception\InvalidArgumentException('Gray must be between 0 and 100'); - } - - $this->gray = (int) $gray; - } - - /** - * Returns the gray value. - * - * @return integer - */ - public function getGray() - { - return $this->gray; - } - - /** - * toRgb(): defined by ColorInterface. - * - * @see ColorInterface::toRgb() - * @return Rgb - */ - public function toRgb() - { - return new Rgb($this->gray * 2.55, $this->gray * 2.55, $this->gray * 2.55); - } - - /** - * toCmyk(): defined by ColorInterface. - * - * @see ColorInterface::toCmyk() - * @return Cmyk - */ - public function toCmyk() - { - return new Cmyk(0, 0, 0, 100 - $this->gray); - } - - /** - * toGray(): defined by ColorInterface. - * - * @see ColorInterface::toGray() - * @return Gray - */ - public function toGray() - { - return $this; - } -} \ No newline at end of file diff --git a/src/BaconQrCode/Renderer/Color/Rgb.php b/src/BaconQrCode/Renderer/Color/Rgb.php deleted file mode 100644 index 44e4060..0000000 --- a/src/BaconQrCode/Renderer/Color/Rgb.php +++ /dev/null @@ -1,148 +0,0 @@ - 255) { - throw new Exception\InvalidArgumentException('Red must be between 0 and 255'); - } - - if ($green < 0 || $green > 255) { - throw new Exception\InvalidArgumentException('Green must be between 0 and 255'); - } - - if ($blue < 0 || $blue > 255) { - throw new Exception\InvalidArgumentException('Blue must be between 0 and 255'); - } - - $this->red = (int) $red; - $this->green = (int) $green; - $this->blue = (int) $blue; - } - - /** - * Returns the red value. - * - * @return integer - */ - public function getRed() - { - return $this->red; - } - - /** - * Returns the green value. - * - * @return integer - */ - public function getGreen() - { - return $this->green; - } - - /** - * Returns the blue value. - * - * @return integer - */ - public function getBlue() - { - return $this->blue; - } - - /** - * Returns a hexadecimal string representation of the RGB value. - * - * @return string - */ - public function __toString() - { - return sprintf('%02x%02x%02x', $this->red, $this->green, $this->blue); - } - - /** - * toRgb(): defined by ColorInterface. - * - * @see ColorInterface::toRgb() - * @return Rgb - */ - public function toRgb() - { - return $this; - } - - /** - * toCmyk(): defined by ColorInterface. - * - * @see ColorInterface::toCmyk() - * @return Cmyk - */ - public function toCmyk() - { - $c = 1 - ($this->red / 255); - $m = 1 - ($this->green / 255); - $y = 1 - ($this->blue / 255); - $k = min($c, $m, $y); - - return new Cmyk( - 100 * ($c - $k) / (1 - $k), - 100 * ($m - $k) / (1 - $k), - 100 * ($y - $k) / (1 - $k), - 100 * $k - ); - } - - /** - * toGray(): defined by ColorInterface. - * - * @see ColorInterface::toGray() - * @return Gray - */ - public function toGray() - { - return new Gray(($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55); - } -} \ No newline at end of file diff --git a/src/BaconQrCode/Renderer/Image/AbstractRenderer.php b/src/BaconQrCode/Renderer/Image/AbstractRenderer.php deleted file mode 100644 index b0bb02a..0000000 --- a/src/BaconQrCode/Renderer/Image/AbstractRenderer.php +++ /dev/null @@ -1,338 +0,0 @@ -margin = (int) $margin; - return $this; - } - - /** - * Gets the margin around the QR code. - * - * @return integer - */ - public function getMargin() - { - return $this->margin; - } - - /** - * Sets the height around the renderd image. - * - * If the width is smaller than the matrix width plus padding, the renderer - * will automatically use that as the width instead of the specified one. - * - * @param integer $width - * @return AbstractRenderer - */ - public function setWidth($width) - { - $this->width = (int) $width; - return $this; - } - - /** - * Gets the width of the rendered image. - * - * @return integer - */ - public function getWidth() - { - return $this->width; - } - - /** - * Sets the height around the renderd image. - * - * If the height is smaller than the matrix height plus padding, the - * renderer will automatically use that as the height instead of the - * specified one. - * - * @param integer $height - * @return AbstractRenderer - */ - public function setHeight($height) - { - $this->height = (int) $height; - return $this; - } - - /** - * Gets the height around the rendered image. - * - * @return integer - */ - public function getHeight() - { - return $this->height; - } - - /** - * Sets whether dimensions should be rounded down. - * - * @param boolean $flag - * @return AbstractRenderer - */ - public function setRoundDimensions($flag) - { - $this->floorToClosestDimension = $flag; - return $this; - } - - /** - * Gets whether dimensions should be rounded down. - * - * @return boolean - */ - public function shouldRoundDimensions() - { - return $this->floorToClosestDimension; - } - - /** - * Sets background color. - * - * @param Color\ColorInterface $color - * @return AbstractRenderer - */ - public function setBackgroundColor(Color\ColorInterface $color) - { - $this->backgroundColor = $color; - return $this; - } - - /** - * Gets background color. - * - * @return Color\ColorInterface - */ - public function getBackgroundColor() - { - if ($this->backgroundColor === null) { - $this->backgroundColor = new Color\Gray(100); - } - - return $this->backgroundColor; - } - - /** - * Sets foreground color. - * - * @param Color\ColorInterface $color - * @return AbstractRenderer - */ - public function setForegroundColor(Color\ColorInterface $color) - { - $this->foregroundColor = $color; - return $this; - } - - /** - * Gets foreground color. - * - * @return Color\ColorInterface - */ - public function getForegroundColor() - { - if ($this->foregroundColor === null) { - $this->foregroundColor = new Color\Gray(0); - } - - return $this->foregroundColor; - } - - /** - * Adds a decorator to the renderer. - * - * @param DecoratorInterface $decorator - * @return AbstractRenderer - */ - public function addDecorator(DecoratorInterface $decorator) - { - $this->decorators[] = $decorator; - return $this; - } - - /** - * render(): defined by RendererInterface. - * - * @see RendererInterface::render() - * @param QrCode $qrCode - * @return string - */ - public function render(QrCode $qrCode) - { - $input = $qrCode->getMatrix(); - $inputWidth = $input->getWidth(); - $inputHeight = $input->getHeight(); - $qrWidth = $inputWidth + ($this->getMargin() << 1); - $qrHeight = $inputHeight + ($this->getMargin() << 1); - $outputWidth = max($this->getWidth(), $qrWidth); - $outputHeight = max($this->getHeight(), $qrHeight); - $multiple = (int) min($outputWidth / $qrWidth, $outputHeight / $qrHeight); - - if ($this->shouldRoundDimensions()) { - $outputWidth -= $outputWidth % $multiple; - $outputHeight -= $outputHeight % $multiple; - } - - // Padding includes both the quiet zone and the extra white pixels to - // accommodate the requested dimensions. For example, if input is 25x25 - // the QR will be 33x33 including the quiet zone. If the requested size - // is 200x160, the multiple will be 4, for a QR of 132x132. These will - // handle all the padding from 100x100 (the actual QR) up to 200x160. - $leftPadding = (int) (($outputWidth - ($inputWidth * $multiple)) / 2); - $topPadding = (int) (($outputHeight - ($inputHeight * $multiple)) / 2); - - // Store calculated parameters - $this->finalWidth = $outputWidth; - $this->finalHeight = $outputHeight; - $this->blockSize = $multiple; - - $this->init(); - $this->addColor('background', $this->getBackgroundColor()); - $this->addColor('foreground', $this->getForegroundColor()); - $this->drawBackground('background'); - - foreach ($this->decorators as $decorator) { - $decorator->preProcess( - $qrCode, - $this, - $outputWidth, - $outputHeight, - $leftPadding, - $topPadding, - $multiple - ); - } - - for ($inputY = 0, $outputY = $topPadding; $inputY < $inputHeight; $inputY++, $outputY += $multiple) { - for ($inputX = 0, $outputX = $leftPadding; $inputX < $inputWidth; $inputX++, $outputX += $multiple) { - if ($input->get($inputX, $inputY) === 1) { - $this->drawBlock($outputX, $outputY, 'foreground'); - } - } - } - - foreach ($this->decorators as $decorator) { - $decorator->postProcess( - $qrCode, - $this, - $outputWidth, - $outputHeight, - $leftPadding, - $topPadding, - $multiple - ); - } - - return $this->getByteStream(); - } -} \ No newline at end of file diff --git a/src/BaconQrCode/Renderer/Image/Decorator/DecoratorInterface.php b/src/BaconQrCode/Renderer/Image/Decorator/DecoratorInterface.php deleted file mode 100644 index e67268b..0000000 --- a/src/BaconQrCode/Renderer/Image/Decorator/DecoratorInterface.php +++ /dev/null @@ -1,63 +0,0 @@ -outerColor = $color; - return $this; - } - - /** - * Gets outer color. - * - * @return Color\ColorInterface - */ - public function getOuterColor() - { - if ($this->outerColor === null) { - $this->outerColor = new Color\Gray(100); - } - - return $this->outerColor; - } - - /** - * Sets inner color. - * - * @param Color\ColorInterface $color - * @return FinderPattern - */ - public function setInnerColor(Color\ColorInterface $color) - { - $this->innerColor = $color; - return $this; - } - - /** - * Gets inner color. - * - * @return Color\ColorInterface - */ - public function getInnerColor() - { - if ($this->innerColor === null) { - $this->innerColor = new Color\Gray(0); - } - - return $this->innerColor; - } - - /** - * preProcess(): defined by DecoratorInterface. - * - * @see DecoratorInterface::preProcess() - * @param QrCode $qrCode - * @param RendererInterface $renderer - * @param integer $outputWidth - * @param integer $outputHeight - * @param integer $leftPadding - * @param integer $topPadding - * @param integer $multiple - * @return void - */ - public function preProcess( - QrCode $qrCode, - RendererInterface $renderer, - $outputWidth, - $outputHeight, - $leftPadding, - $topPadding, - $multiple - ) { - $matrix = $qrCode->getMatrix(); - $positions = array( - array(0, 0), - array($matrix->getWidth() - 7, 0), - array(0, $matrix->getHeight() - 7), - ); - - foreach (self::$outerPositionDetectionPattern as $y => $row) { - foreach ($row as $x => $isSet) { - foreach ($positions as $position) { - $matrix->set($x + $position[0], $y + $position[1], 0); - } - } - } - } - - /** - * postProcess(): defined by DecoratorInterface. - * - * @see DecoratorInterface::postProcess() - * - * @param QrCode $qrCode - * @param RendererInterface $renderer - * @param integer $outputWidth - * @param integer $outputHeight - * @param integer $leftPadding - * @param integer $topPadding - * @param integer $multiple - * @return void - */ - public function postProcess( - QrCode $qrCode, - RendererInterface $renderer, - $outputWidth, - $outputHeight, - $leftPadding, - $topPadding, - $multiple - ) { - $matrix = $qrCode->getMatrix(); - $positions = array( - array(0, 0), - array($matrix->getWidth() - 7, 0), - array(0, $matrix->getHeight() - 7), - ); - - $renderer->addColor('finder-outer', $this->getOuterColor()); - $renderer->addColor('finder-inner', $this->getInnerColor()); - - foreach (self::$outerPositionDetectionPattern as $y => $row) { - foreach ($row as $x => $isOuterSet) { - $isInnerSet = self::$innerPositionDetectionPattern[$y][$x]; - - if ($isOuterSet) { - foreach ($positions as $position) { - $renderer->drawBlock( - $leftPadding + $x * $multiple + $position[0] * $multiple, - $topPadding + $y * $multiple + $position[1] * $multiple, - 'finder-outer' - ); - } - } - - if ($isInnerSet) { - foreach ($positions as $position) { - $renderer->drawBlock( - $leftPadding + $x * $multiple + $position[0] * $multiple, - $topPadding + $y * $multiple + $position[1] * $multiple, - 'finder-inner' - ); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/BaconQrCode/Renderer/Image/Eps.php b/src/BaconQrCode/Renderer/Image/Eps.php deleted file mode 100644 index 9766195..0000000 --- a/src/BaconQrCode/Renderer/Image/Eps.php +++ /dev/null @@ -1,152 +0,0 @@ -eps = "%!PS-Adobe-3.0 EPSF-3.0\n" - . "%%BoundingBox: 0 0 " . $this->finalWidth . " " . $this->finalHeight . "\n" - . "/F { rectfill } def\n"; - } - - /** - * addColor(): defined by RendererInterface. - * - * @see ImageRendererInterface::addColor() - * @param string $id - * @param ColorInterface $color - * @return void - */ - public function addColor($id, ColorInterface $color) - { - if ( - !$color instanceof Rgb - && !$color instanceof Cmyk - && !$color instanceof Gray - ) { - $color = $color->toCmyk(); - } - - $this->colors[$id] = $color; - } - - /** - * drawBackground(): defined by RendererInterface. - * - * @see ImageRendererInterface::drawBackground() - * @param string $colorId - * @return void - */ - public function drawBackground($colorId) - { - $this->setColor($colorId); - $this->eps .= "0 0 " . $this->finalWidth . " " . $this->finalHeight . " F\n"; - } - - /** - * drawBlock(): defined by RendererInterface. - * - * @see ImageRendererInterface::drawBlock() - * @param integer $x - * @param integer $y - * @param string $colorId - * @return void - */ - public function drawBlock($x, $y, $colorId) - { - $this->setColor($colorId); - $this->eps .= $x . " " . ($this->finalHeight - $y - $this->blockSize) . " " . $this->blockSize . " " . $this->blockSize . " F\n"; - } - - /** - * getByteStream(): defined by RendererInterface. - * - * @see ImageRendererInterface::getByteStream() - * @return string - */ - public function getByteStream() - { - return $this->eps; - } - - /** - * Sets color to use. - * - * @param string $colorId - * @return void - */ - protected function setColor($colorId) - { - if ($colorId !== $this->currentColor) { - $color = $this->colors[$colorId]; - - if ($color instanceof Rgb) { - $this->eps .= sprintf( - "%F %F %F setrgbcolor\n", - $color->getRed() / 100, - $color->getGreen() / 100, - $color->getBlue() / 100 - ); - } elseif ($color instanceof Cmyk) { - $this->eps .= sprintf( - "%F %F %F %F setcmykcolor\n", - $color->getCyan() / 100, - $color->getMagenta() / 100, - $color->getYellow() / 100, - $color->getBlack() / 100 - ); - } elseif ($color instanceof Gray) { - $this->eps .= sprintf( - "%F setgray\n", - $color->getGray() / 100 - ); - } - - $this->currentColor = $colorId; - } - } -} diff --git a/src/BaconQrCode/Renderer/Image/Png.php b/src/BaconQrCode/Renderer/Image/Png.php deleted file mode 100644 index dd593a8..0000000 --- a/src/BaconQrCode/Renderer/Image/Png.php +++ /dev/null @@ -1,115 +0,0 @@ -image = imagecreatetruecolor($this->finalWidth, $this->finalHeight); - } - - /** - * addColor(): defined by RendererInterface. - * - * @see ImageRendererInterface::addColor() - * @param string $id - * @param ColorInterface $color - * @return void - * @throws Exception\RuntimeException - */ - public function addColor($id, ColorInterface $color) - { - if ($this->image === null) { - throw new Exception\RuntimeException('Colors can only be added after init'); - } - - $color = $color->toRgb(); - - $this->colors[$id] = imagecolorallocate( - $this->image, - $color->getRed(), - $color->getGreen(), - $color->getBlue() - ); - } - - /** - * drawBackground(): defined by RendererInterface. - * - * @see ImageRendererInterface::drawBackground() - * @param string $colorId - * @return void - */ - public function drawBackground($colorId) - { - imagefill($this->image, 0, 0, $this->colors[$colorId]); - } - - /** - * drawBlock(): defined by RendererInterface. - * - * @see ImageRendererInterface::drawBlock() - * @param integer $x - * @param integer $y - * @param string $colorId - * @return void - */ - public function drawBlock($x, $y, $colorId) - { - imagefilledrectangle( - $this->image, - $x, - $y, - $x + $this->blockSize - 1, - $y + $this->blockSize - 1, - $this->colors[$colorId] - ); - } - - /** - * getByteStream(): defined by RendererInterface. - * - * @see ImageRendererInterface::getByteStream() - * @return string - */ - public function getByteStream() - { - ob_start(); - imagepng($this->image); - return ob_get_clean(); - } -} \ No newline at end of file diff --git a/src/BaconQrCode/Renderer/Image/RendererInterface.php b/src/BaconQrCode/Renderer/Image/RendererInterface.php deleted file mode 100644 index 52101a6..0000000 --- a/src/BaconQrCode/Renderer/Image/RendererInterface.php +++ /dev/null @@ -1,61 +0,0 @@ -svg = new SimpleXMLElement( - '' - . '' - ); - $this->svg->addAttribute('version', '1.1'); - $this->svg->addAttribute('width', $this->finalWidth . 'px'); - $this->svg->addAttribute('height', $this->finalHeight . 'px'); - $this->svg->addAttribute('viewBox', '0 0 ' . $this->finalWidth . ' ' . $this->finalHeight); - $this->svg->addChild('defs'); - } - - /** - * addColor(): defined by RendererInterface. - * - * @see ImageRendererInterface::addColor() - * @param string $id - * @param ColorInterface $color - * @return void - * @throws Exception\InvalidArgumentException - */ - public function addColor($id, ColorInterface $color) - { - $this->colors[$id] = (string) $color->toRgb(); - } - - /** - * drawBackground(): defined by RendererInterface. - * - * @see ImageRendererInterface::drawBackground() - * @param string $colorId - * @return void - */ - public function drawBackground($colorId) - { - $rect = $this->svg->addChild('rect'); - $rect->addAttribute('x', 0); - $rect->addAttribute('y', 0); - $rect->addAttribute('width', $this->finalWidth); - $rect->addAttribute('height', $this->finalHeight); - $rect->addAttribute('fill', '#' . $this->colors[$colorId]); - } - - /** - * drawBlock(): defined by RendererInterface. - * - * @see ImageRendererInterface::drawBlock() - * @param integer $x - * @param integer $y - * @param string $colorId - * @return void - */ - public function drawBlock($x, $y, $colorId) - { - $use = $this->svg->addChild('use'); - $use->addAttribute('x', $x); - $use->addAttribute('y', $y); - $use->addAttribute( - 'xlink:href', - $this->getRectPrototypeId($colorId), - 'http://www.w3.org/1999/xlink' - ); - } - - /** - * getByteStream(): defined by RendererInterface. - * - * @see ImageRendererInterface::getByteStream() - * @return string - */ - public function getByteStream() - { - return $this->svg->asXML(); - } - - /** - * Get the prototype ID for a color. - * - * @param integer $colorId - * @return string - */ - protected function getRectPrototypeId($colorId) - { - if (!isset($this->prototypeIds[$colorId])) { - $id = 'r' . dechex(count($this->prototypeIds)); - - $rect = $this->svg->defs->addChild('rect'); - $rect->addAttribute('id', $id); - $rect->addAttribute('width', $this->blockSize); - $rect->addAttribute('height', $this->blockSize); - $rect->addAttribute('fill', '#' . $this->colors[$colorId]); - - $this->prototypeIds[$colorId] = '#' . $id; - } - - return $this->prototypeIds[$colorId]; - } -} diff --git a/src/BaconQrCode/Renderer/RendererInterface.php b/src/BaconQrCode/Renderer/RendererInterface.php deleted file mode 100644 index 554e1d8..0000000 --- a/src/BaconQrCode/Renderer/RendererInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -class = $class; - } - - /** - * Get CSS class name. - * - * @return string - */ - public function getClass() - { - return $this->class; - } - - /** - * Set CSS style value. - * - * @param string $style - */ - public function setStyle($style) - { - $this->style = $style; - } - - /** - * Get CSS style value. - * - * @return string - */ - public function getStyle() - { - return $this->style; - } - - /** - * render(): defined by RendererInterface. - * - * @see RendererInterface::render() - * @param QrCode $qrCode - * @return string - */ - public function render(QrCode $qrCode) - { - $textCode = parent::render($qrCode); - - $result = '' . $textCode . ''; - - return $result; - } -} diff --git a/src/BaconQrCode/Renderer/Text/Plain.php b/src/BaconQrCode/Renderer/Text/Plain.php deleted file mode 100644 index a7e4cfb..0000000 --- a/src/BaconQrCode/Renderer/Text/Plain.php +++ /dev/null @@ -1,150 +0,0 @@ -fullBlock = $fullBlock; - } - - /** - * Get char used as full block (occupied space, "black"). - * - * @return string - */ - public function getFullBlock() - { - return $this->fullBlock; - } - - /** - * Set char used as empty block (empty space, "white"). - * - * @param string $emptyBlock - */ - public function setEmptyBlock($emptyBlock) - { - $this->emptyBlock = $emptyBlock; - } - - /** - * Get char used as empty block (empty space, "white"). - * - * @return string - */ - public function getEmptyBlock() - { - return $this->emptyBlock; - } - - /** - * Sets the margin around the QR code. - * - * @param integer $margin - * @return AbstractRenderer - * @throws Exception\InvalidArgumentException - */ - public function setMargin($margin) - { - if ($margin < 0) { - throw new Exception\InvalidArgumentException('Margin must be equal to greater than 0'); - } - - $this->margin = (int) $margin; - - return $this; - } - - /** - * Gets the margin around the QR code. - * - * @return integer - */ - public function getMargin() - { - return $this->margin; - } - - /** - * render(): defined by RendererInterface. - * - * @see RendererInterface::render() - * @param QrCode $qrCode - * @return string - */ - public function render(QrCode $qrCode) - { - $result = ''; - $matrix = $qrCode->getMatrix(); - $width = $matrix->getWidth(); - - // Top margin - for ($x = 0; $x < $this->margin; $x++) { - $result .= str_repeat($this->emptyBlock, $width + 2 * $this->margin)."\n"; - } - - // Body - $array = $matrix->getArray(); - - foreach ($array as $row) { - $result .= str_repeat($this->emptyBlock, $this->margin); // left margin - foreach ($row as $byte) { - $result .= $byte ? $this->fullBlock : $this->emptyBlock; - } - $result .= str_repeat($this->emptyBlock, $this->margin); // right margin - $result .= "\n"; - } - - // Bottom margin - for ($x = 0; $x < $this->margin; $x++) { - $result .= str_repeat($this->emptyBlock, $width + 2 * $this->margin)."\n"; - } - - return $result; - } -} diff --git a/src/BaconQrCode/Writer.php b/src/BaconQrCode/Writer.php deleted file mode 100644 index 0f80313..0000000 --- a/src/BaconQrCode/Writer.php +++ /dev/null @@ -1,105 +0,0 @@ -renderer = $renderer; - } - - /** - * Sets the renderer used to create a byte stream. - * - * @param RendererInterface $renderer - * @return Writer - */ - public function setRenderer(RendererInterface $renderer) - { - $this->renderer = $renderer; - return $this; - } - - /** - * Gets the renderer used to create a byte stream. - * - * @return RendererInterface - */ - public function getRenderer() - { - return $this->renderer; - } - - /** - * Writes QR code and returns it as string. - * - * Content is a string which *should* be encoded in UTF-8, in case there are - * non ASCII-characters present. - * - * @param string $content - * @param string $encoding - * @param integer $ecLevel - * @return string - * @throws Exception\InvalidArgumentException - */ - public function writeString( - $content, - $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, - $ecLevel = ErrorCorrectionLevel::L - ) { - if (strlen($content) === 0) { - throw new Exception\InvalidArgumentException('Found empty contents'); - } - - $qrCode = Encoder::encode($content, new ErrorCorrectionLevel($ecLevel), $encoding); - - return $this->getRenderer()->render($qrCode); - } - - /** - * Writes QR code to a file. - * - * @see Writer::writeString() - * @param string $content - * @param string $filename - * @param string $encoding - * @param integer $ecLevel - * @return void - */ - public function writeFile( - $content, - $filename, - $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, - $ecLevel = ErrorCorrectionLevel::L - ) { - file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel)); - } -} diff --git a/src/BaconQrCode/Common/BitArray.php b/src/Common/BitArray.php similarity index 55% rename from src/BaconQrCode/Common/BitArray.php rename to src/Common/BitArray.php index 0a99d9a..158384f 100644 --- a/src/BaconQrCode/Common/BitArray.php +++ b/src/Common/BitArray.php @@ -1,73 +1,59 @@ */ - protected $bits; + private $bits; /** * Size of the bit array in bits. * - * @var integer + * @var int */ - protected $size; + private $size; /** * Creates a new bit array with a given size. - * - * @param integer $size */ - public function __construct($size = 0) + public function __construct(int $size = 0) { $this->size = $size; - $this->bits = new SplFixedArray(($this->size + 31) >> 3); + $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0)); } /** * Gets the size in bits. - * - * @return integer */ - public function getSize() + public function getSize() : int { return $this->size; } /** * Gets the size in bytes. - * - * @return integer */ - public function getSizeInBytes() + public function getSizeInBytes() : int { return ($this->size + 7) >> 3; } /** * Ensures that the array has a minimum capacity. - * - * @param integer $size - * @return void */ - public function ensureCapacity($size) + public function ensureCapacity(int $size) : void { if ($size > count($this->bits) << 5) { $this->bits->setSize(($size + 31) >> 5); @@ -76,56 +62,43 @@ public function ensureCapacity($size) /** * Gets a specific bit. - * - * @param integer $i - * @return boolean */ - public function get($i) + public function get(int $i) : bool { - return ($this->bits[$i >> 5] & (1 << ($i & 0x1f))) !== 0; + return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f))); } /** * Sets a specific bit. - * - * @param integer $i - * @return void */ - public function set($i) + public function set(int $i) : void { $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f); } /** * Flips a specific bit. - * - * @param integer $i - * @return void */ - public function flip($i) + public function flip(int $i) : void { $this->bits[$i >> 5] ^= 1 << ($i & 0x1f); } /** * Gets the next set bit position from a given position. - * - * @param integer $from - * @return integer */ - public function getNextSet($from) + public function getNextSet(int $from) : int { if ($from >= $this->size) { return $this->size; } - $bitsOffset = $from >> 5; + $bitsOffset = $from >> 5; $currentBits = $this->bits[$bitsOffset]; - $bitsLength = count($this->bits); - + $bitsLength = count($this->bits); $currentBits &= ~((1 << ($from & 0x1f)) - 1); - while ($currentBits === 0) { + while (0 === $currentBits) { if (++$bitsOffset === $bitsLength) { return $this->size; } @@ -134,29 +107,24 @@ public function getNextSet($from) } $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); - return $result > $this->size ? $this->size : $result; } /** * Gets the next unset bit position from a given position. - * - * @param integer $from - * @return integer */ - public function getNextUnset($from) + public function getNextUnset(int $from) : int { if ($from >= $this->size) { return $this->size; } - $bitsOffset = $from >> 5; + $bitsOffset = $from >> 5; $currentBits = ~$this->bits[$bitsOffset]; - $bitsLength = count($this->bits); - + $bitsLength = count($this->bits); $currentBits &= ~((1 << ($from & 0x1f)) - 1); - while ($currentBits === 0) { + while (0 === $currentBits) { if (++$bitsOffset === $bitsLength) { return $this->size; } @@ -165,18 +133,13 @@ public function getNextUnset($from) } $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); - return $result > $this->size ? $this->size : $result; } /** * Sets a bulk of bits. - * - * @param integer $i - * @param integer $newBits - * @return void */ - public function setBulk($i, $newBits) + public function setBulk(int $i, int $newBits) : void { $this->bits[$i >> 5] = $newBits; } @@ -184,36 +147,33 @@ public function setBulk($i, $newBits) /** * Sets a range of bits. * - * @param integer $start - * @param integer $end - * @return void - * @throws Exception\InvalidArgumentException + * @throws InvalidArgumentException if end is smaller than start */ - public function setRange($start, $end) + public function setRange(int $start, int $end) : void { if ($end < $start) { - throw new Exception\InvalidArgumentException('End must be greater or equal to start'); + throw new InvalidArgumentException('End must be greater or equal to start'); } if ($end === $start) { return; } - $end--; + --$end; $firstInt = $start >> 5; - $lastInt = $end >> 5; + $lastInt = $end >> 5; - for ($i = $firstInt; $i <= $lastInt; $i++) { - $firstBit = $i > $firstInt ? 0 : $start & 0x1f; - $lastBit = $i < $lastInt ? 31 : $end & 0x1f; + for ($i = $firstInt; $i <= $lastInt; ++$i) { + $firstBit = $i > $firstInt ? 0 : $start & 0x1f; + $lastBit = $i < $lastInt ? 31 : $end & 0x1f; - if ($firstBit === 0 && $lastBit === 31) { + if (0 === $firstBit && 31 === $lastBit) { $mask = 0x7fffffff; } else { $mask = 0; - for ($j = $firstBit; $j < $lastBit; $j++) { + for ($j = $firstBit; $j < $lastBit; ++$j) { $mask |= 1 << $j; } } @@ -224,52 +184,46 @@ public function setRange($start, $end) /** * Clears the bit array, unsetting every bit. - * - * @return void */ - public function clear() + public function clear() : void { $bitsLength = count($this->bits); - for ($i = 0; $i < $bitsLength; $i++) { + for ($i = 0; $i < $bitsLength; ++$i) { $this->bits[$i] = 0; } } /** * Checks if a range of bits is set or not set. - * - * @param integer $start - * @param integer $end - * @param integer $value - * @return boolean - * @throws Exception\InvalidArgumentException + + * @throws InvalidArgumentException if end is smaller than start */ - public function isRange($start, $end, $value) + public function isRange(int $start, int $end, bool $value) : bool { if ($end < $start) { - throw new Exception\InvalidArgumentException('End must be greater or equal to start'); + throw new InvalidArgumentException('End must be greater or equal to start'); } if ($end === $start) { - return; + return true; } - $end--; + --$end; $firstInt = $start >> 5; - $lastInt = $end >> 5; + $lastInt = $end >> 5; - for ($i = $firstInt; $i <= $lastInt; $i++) { - $firstBit = $i > $firstInt ? 0 : $start & 0x1f; - $lastBit = $i < $lastInt ? 31 : $end & 0x1f; + for ($i = $firstInt; $i <= $lastInt; ++$i) { + $firstBit = $i > $firstInt ? 0 : $start & 0x1f; + $lastBit = $i < $lastInt ? 31 : $end & 0x1f; - if ($firstBit === 0 && $lastBit === 31) { + if (0 === $firstBit && 31 === $lastBit) { $mask = 0x7fffffff; } else { $mask = 0; - for ($j = $firstBit; $j <= $lastBit; $j++) { + for ($j = $firstBit; $j <= $lastBit; ++$j) { $mask |= 1 << $j; } } @@ -284,11 +238,8 @@ public function isRange($start, $end, $value) /** * Appends a bit to the array. - * - * @param boolean $bit - * @return void */ - public function appendBit($bit) + public function appendBit(bool $bit) : void { $this->ensureCapacity($this->size + 1); @@ -296,21 +247,18 @@ public function appendBit($bit) $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f)); } - $this->size++; + ++$this->size; } /** * Appends a number of bits (up to 32) to the array. - * - * @param integer $value - * @param integer $numBits - * @return void - * @throws Exception\InvalidArgumentException + + * @throws InvalidArgumentException if num bits is not between 0 and 32 */ - public function appendBits($value, $numBits) + public function appendBits(int $value, int $numBits) : void { if ($numBits < 0 || $numBits > 32) { - throw new Exception\InvalidArgumentException('Num bits must be between 0 and 32'); + throw new InvalidArgumentException('Num bits must be between 0 and 32'); } $this->ensureCapacity($this->size + $numBits); @@ -322,16 +270,13 @@ public function appendBits($value, $numBits) /** * Appends another bit array to this array. - * - * @param BitArray $other - * @return void */ - public function appendBitArray(self $other) + public function appendBitArray(self $other) : void { $otherSize = $other->getSize(); $this->ensureCapacity($this->size + $other->getSize()); - for ($i = 0; $i < $otherSize; $i++) { + for ($i = 0; $i < $otherSize; ++$i) { $this->appendBit($other->get($i)); } } @@ -339,20 +284,18 @@ public function appendBitArray(self $other) /** * Makes an exclusive-or comparision on the current bit array. * - * @param BitArray $other - * @return void - * @throws Exception\InvalidArgumentException + * @throws InvalidArgumentException if sizes don't match */ - public function xorBits(self $other) + public function xorBits(self $other) : void { $bitsLength = count($this->bits); $otherBits = $other->getBitArray(); if ($bitsLength !== count($otherBits)) { - throw new Exception\InvalidArgumentException('Sizes don\'t match'); + throw new InvalidArgumentException('Sizes don\'t match'); } - for ($i = 0; $i < $bitsLength; $i++) { + for ($i = 0; $i < $bitsLength; ++$i) { $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i]; } } @@ -360,23 +303,21 @@ public function xorBits(self $other) /** * Converts the bit array to a byte array. * - * @param integer $bitOffset - * @param integer $numBytes - * @return SplFixedArray + * @return SplFixedArray */ - public function toBytes($bitOffset, $numBytes) + public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray { $bytes = new SplFixedArray($numBytes); - for ($i = 0; $i < $numBytes; $i++) { + for ($i = 0; $i < $numBytes; ++$i) { $byte = 0; - for ($j = 0; $j < 8; $j++) { + for ($j = 0; $j < 8; ++$j) { if ($this->get($bitOffset)) { $byte |= 1 << (7 - $j); } - $bitOffset++; + ++$bitOffset; } $bytes[$i] = $byte; @@ -388,42 +329,38 @@ public function toBytes($bitOffset, $numBytes) /** * Gets the internal bit array. * - * @return SplFixedArray + * @return SplFixedArray */ - public function getBitArray() + public function getBitArray() : SplFixedArray { return $this->bits; } /** * Reverses the array. - * - * @return void */ - public function reverse() + public function reverse() : void { $newBits = new SplFixedArray(count($this->bits)); - for ($i = 0; $i < $this->size; $i++) { + for ($i = 0; $i < $this->size; ++$i) { if ($this->get($this->size - $i - 1)) { $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f)); } } - $this->bits = newBits; + $this->bits = $newBits; } /** * Returns a string representation of the bit array. - * - * @return string */ - public function __toString() + public function __toString() : string { $result = ''; - for ($i = 0; $i < $this->size; $i++) { - if (($i & 0x07) === 0) { + for ($i = 0; $i < $this->size; ++$i) { + if (0 === ($i & 0x07)) { $result .= ' '; } diff --git a/src/BaconQrCode/Common/BitMatrix.php b/src/Common/BitMatrix.php similarity index 53% rename from src/BaconQrCode/Common/BitMatrix.php rename to src/Common/BitMatrix.php index b930f88..10bf8fe 100644 --- a/src/BaconQrCode/Common/BitMatrix.php +++ b/src/Common/BitMatrix.php @@ -1,14 +1,9 @@ */ - protected $bits; + private $bits; /** - * Creates a new bit matrix with given dimensions. - * - * @param integer $width - * @param integer|null $height - * @throws Exception\InvalidArgumentException + * @throws InvalidArgumentException if a dimension is smaller than zero */ - public function __construct($width, $height = null) + public function __construct(int $width, int $height = null) { - if ($height === null) { + if (null === $height) { $height = $width; } if ($width < 1 || $height < 1) { - throw new Exception\InvalidArgumentException('Both dimensions must be greater than zero'); + throw new InvalidArgumentException('Both dimensions must be greater than zero'); } - $this->width = $width; - $this->height = $height; + $this->width = $width; + $this->height = $height; $this->rowSize = ($width + 31) >> 5; - $this->bits = new SplFixedArray($this->rowSize * $height); + $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0)); } /** * Gets the requested bit, where true means black. - * - * @param integer $x - * @param integer $y - * @return boolean */ - public function get($x, $y) + public function get(int $x, int $y) : bool { $offset = $y * $this->rowSize + ($x >> 5); - return (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1) !== 0; + return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1); } /** * Sets the given bit to true. - * - * @param integer $x - * @param integer $y - * @return void */ - public function set($x, $y) + public function set(int $x, int $y) : void { $offset = $y * $this->rowSize + ($x >> 5); $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f)); @@ -99,12 +82,8 @@ public function set($x, $y) /** * Flips the given bit. - * - * @param integer $x - * @param integer $y - * @return void */ - public function flip($x, $y) + public function flip(int $x, int $y) : void { $offset = $y * $this->rowSize + ($x >> 5); $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f)); @@ -112,14 +91,12 @@ public function flip($x, $y) /** * Clears all bits (set to false). - * - * @return void */ - public function clear() + public function clear() : void { $max = count($this->bits); - for ($i = 0; $i < $max; $i++) { + for ($i = 0; $i < $max; ++$i) { $this->bits[$i] = 0; } } @@ -127,33 +104,31 @@ public function clear() /** * Sets a square region of the bit matrix to true. * - * @param integer $left - * @param integer $top - * @param integer $width - * @param integer $height - * @return void + * @throws InvalidArgumentException if left or top are negative + * @throws InvalidArgumentException if width or height are smaller than 1 + * @throws InvalidArgumentException if region does not fit into the matix */ - public function setRegion($left, $top, $width, $height) + public function setRegion(int $left, int $top, int $width, int $height) : void { if ($top < 0 || $left < 0) { - throw new Exception\InvalidArgumentException('Left and top must be non-negative'); + throw new InvalidArgumentException('Left and top must be non-negative'); } if ($height < 1 || $width < 1) { - throw new Exception\InvalidArgumentException('Width and height must be at least 1'); + throw new InvalidArgumentException('Width and height must be at least 1'); } - $right = $left + $width; + $right = $left + $width; $bottom = $top + $height; if ($bottom > $this->height || $right > $this->width) { - throw new Exception\InvalidArgumentException('The region must fit inside the matrix'); + throw new InvalidArgumentException('The region must fit inside the matrix'); } - for ($y = $top; $y < $bottom; $y++) { + for ($y = $top; $y < $bottom; ++$y) { $offset = $y * $this->rowSize; - for ($x = $left; $x < $right; $x++) { + for ($x = $left; $x < $right; ++$x) { $index = $offset + ($x >> 5); $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f)); } @@ -162,20 +137,16 @@ public function setRegion($left, $top, $width, $height) /** * A fast method to retrieve one row of data from the matrix as a BitArray. - * - * @param integer $y - * @param BitArray $row - * @return BitArray */ - public function getRow($y, BitArray $row = null) + public function getRow(int $y, BitArray $row = null) : BitArray { - if ($row === null || $row->getSize() < $this->width) { + if (null === $row || $row->getSize() < $this->width) { $row = new BitArray($this->width); } $offset = $y * $this->rowSize; - for ($x = 0; $x < $this->rowSize; $x++) { + for ($x = 0; $x < $this->rowSize; ++$x) { $row->setBulk($x << 5, $this->bits[$offset + $x]); } @@ -184,16 +155,12 @@ public function getRow($y, BitArray $row = null) /** * Sets a row of data from a BitArray. - * - * @param integer $y - * @param BitArray $row - * @return void */ - public function setRow($y, BitArray $row) + public function setRow(int $y, BitArray $row) : void { $bits = $row->getBitArray(); - for ($i = 0; $i < $this->rowSize; $i++) { + for ($i = 0; $i < $this->rowSize; ++$i) { $this->bits[$y * $this->rowSize + $i] = $bits[$i]; } } @@ -201,20 +168,20 @@ public function setRow($y, BitArray $row) /** * This is useful in detecting the enclosing rectangle of a 'pure' barcode. * - * @return SplFixedArray + * @return int[]|null */ - public function getEnclosingRectangle() + public function getEnclosingRectangle() : ?array { - $left = $this->width; - $top = $this->height; - $right = -1; + $left = $this->width; + $top = $this->height; + $right = -1; $bottom = -1; - for ($y = 0; $y < $this->height; $y++) { - for ($x32 = 0; $x32 < $this->rowSize; $x32++) { + for ($y = 0; $y < $this->height; ++$y) { + for ($x32 = 0; $x32 < $this->rowSize; ++$x32) { $bits = $this->bits[$y * $this->rowSize + $x32]; - if ($bits !== 0) { + if (0 !== $bits) { if ($y < $top) { $top = $y; } @@ -239,8 +206,8 @@ public function getEnclosingRectangle() if ($x32 * 32 + 31 > $right) { $bit = 31; - while (BitUtils::unsignedRightShift($bits, $bit) === 0) { - $bit--; + while (0 === BitUtils::unsignedRightShift($bits, $bit)) { + --$bit; } if (($x32 * 32 + $bit) > $right) { @@ -250,14 +217,14 @@ public function getEnclosingRectangle() } } - $width = $right - $left; + $width = $right - $left; $height = $bottom - $top; if ($width < 0 || $height < 0) { return null; } - return SplFixedArray::fromArray(array($left, $top, $width, $height), false); + return [$left, $top, $width, $height]; } /** @@ -265,33 +232,33 @@ public function getEnclosingRectangle() * * This is useful in detecting a corner of a 'pure' barcode. * - * @return SplFixedArray + * @return int[]|null */ - public function getTopLeftOnBit() + public function getTopLeftOnBit() : ?array { $bitsOffset = 0; - while ($bitsOffset < count($this->bits) && $this->bits[$bitsOffset] === 0) { - $bitsOffset++; + while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) { + ++$bitsOffset; } - if ($bitsOffset === count($this->bits)) { + if (count($this->bits) === $bitsOffset) { return null; } - $x = intval($bitsOffset / $this->rowSize); + $x = intdiv($bitsOffset, $this->rowSize); $y = ($bitsOffset % $this->rowSize) << 5; $bits = $this->bits[$bitsOffset]; - $bit = 0; + $bit = 0; - while (($bits << (31 - $bit)) === 0) { - $bit++; + while (0 === ($bits << (31 - $bit))) { + ++$bit; } $x += $bit; - return SplFixedArray::fromArray(array($x, $y), false); + return [$x, $y]; } /** @@ -299,51 +266,47 @@ public function getTopLeftOnBit() * * This is useful in detecting a corner of a 'pure' barcode. * - * @return SplFixedArray + * @return int[]|null */ - public function getBottomRightOnBit() + public function getBottomRightOnBit() : ?array { $bitsOffset = count($this->bits) - 1; - while ($bitsOffset >= 0 && $this->bits[$bitsOffset] === 0) { - $bitsOffset--; + while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) { + --$bitsOffset; } if ($bitsOffset < 0) { return null; } - $x = intval($bitsOffset / $this->rowSize); + $x = intdiv($bitsOffset, $this->rowSize); $y = ($bitsOffset % $this->rowSize) << 5; $bits = $this->bits[$bitsOffset]; $bit = 0; - while (BitUtils::unsignedRightShift($bits, $bit) === 0) { - $bit--; + while (0 === BitUtils::unsignedRightShift($bits, $bit)) { + --$bit; } $x += $bit; - return SplFixedArray::fromArray(array($x, $y), false); + return [$x, $y]; } /** * Gets the width of the matrix, - * - * @return integer */ - public function getWidth() + public function getWidth() : int { return $this->width; } /** * Gets the height of the matrix. - * - * @return integer */ - public function getHeight() + public function getHeight() : int { return $this->height; } diff --git a/src/BaconQrCode/Common/BitUtils.php b/src/Common/BitUtils.php similarity index 57% rename from src/BaconQrCode/Common/BitUtils.php rename to src/Common/BitUtils.php index a641244..0c575b4 100644 --- a/src/BaconQrCode/Common/BitUtils.php +++ b/src/Common/BitUtils.php @@ -1,11 +1,5 @@ >>" in other * languages. - * - * @param integer $a - * @param integer $b - * @return integer */ - public static function unsignedRightShift($a, $b) + public static function unsignedRightShift(int $a, int $b) : int { return ( $a >= 0 @@ -38,14 +32,10 @@ public static function unsignedRightShift($a, $b) /** * Gets the number of trailing zeros. - * - * @param integer $i - * @return integer */ - public static function numberOfTrailingZeros($i) + public static function numberOfTrailingZeros(int $i) : int { $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1'); - return $lastPos === false ? 32 : 31 - $lastPos; } -} \ No newline at end of file +} diff --git a/src/Common/CharacterSetEci.php b/src/Common/CharacterSetEci.php new file mode 100644 index 0000000..6dfff17 --- /dev/null +++ b/src/Common/CharacterSetEci.php @@ -0,0 +1,180 @@ +|null + */ + private static $valueToEci; + + /** + * @var array|null + */ + private static $nameToEci; + + public function __construct(array $values, string ...$otherEncodingNames) + { + $this->values = $values; + $this->otherEncodingNames = $otherEncodingNames; + } + + /** + * Returns the primary value. + */ + public function getValue() : int + { + return $this->values[0]; + } + + /** + * Gets character set ECI by value. + * + * Returns the representing ECI of a given value, or null if it is legal but unsupported. + * + * @throws InvalidArgumentException if value is not between 0 and 900 + */ + public static function getCharacterSetEciByValue(int $value) : ?self + { + if ($value < 0 || $value >= 900) { + throw new InvalidArgumentException('Value must be between 0 and 900'); + } + + $valueToEci = self::valueToEci(); + + if (! array_key_exists($value, $valueToEci)) { + return null; + } + + return $valueToEci[$value]; + } + + /** + * Returns character set ECI by name. + * + * Returns the representing ECI of a given name, or null if it is legal but unsupported + */ + public static function getCharacterSetEciByName(string $name) : ?self + { + $nameToEci = self::nameToEci(); + $name = strtolower($name); + + if (! array_key_exists($name, $nameToEci)) { + return null; + } + + return $nameToEci[$name]; + } + + private static function valueToEci() : array + { + if (null !== self::$valueToEci) { + return self::$valueToEci; + } + + self::$valueToEci = []; + + foreach (self::values() as $eci) { + foreach ($eci->values as $value) { + self::$valueToEci[$value] = $eci; + } + } + + return self::$valueToEci; + } + + private static function nameToEci() : array + { + if (null !== self::$nameToEci) { + return self::$nameToEci; + } + + self::$nameToEci = []; + + foreach (self::values() as $eci) { + self::$nameToEci[strtolower($eci->name())] = $eci; + + foreach ($eci->otherEncodingNames as $name) { + self::$nameToEci[strtolower($name)] = $eci; + } + } + + return self::$nameToEci; + } +} diff --git a/src/Common/EcBlock.php b/src/Common/EcBlock.php new file mode 100644 index 0000000..a9a1d07 --- /dev/null +++ b/src/Common/EcBlock.php @@ -0,0 +1,49 @@ +count = $count; + $this->dataCodewords = $dataCodewords; + } + + /** + * Returns how many times the block is used. + */ + public function getCount() : int + { + return $this->count; + } + + /** + * Returns the number of data codewords. + */ + public function getDataCodewords() : int + { + return $this->dataCodewords; + } +} diff --git a/src/Common/EcBlocks.php b/src/Common/EcBlocks.php new file mode 100644 index 0000000..172b5f2 --- /dev/null +++ b/src/Common/EcBlocks.php @@ -0,0 +1,74 @@ +ecCodewordsPerBlock = $ecCodewordsPerBlock; + $this->ecBlocks = $ecBlocks; + } + + /** + * Returns the number of EC codewords per block. + */ + public function getEcCodewordsPerBlock() : int + { + return $this->ecCodewordsPerBlock; + } + + /** + * Returns the total number of EC block appearances. + */ + public function getNumBlocks() : int + { + $total = 0; + + foreach ($this->ecBlocks as $ecBlock) { + $total += $ecBlock->getCount(); + } + + return $total; + } + + /** + * Returns the total count of EC codewords. + */ + public function getTotalEcCodewords() : int + { + return $this->ecCodewordsPerBlock * $this->getNumBlocks(); + } + + /** + * Returns the EC blocks included in this collection. + * + * @return EcBlock[] + */ + public function getEcBlocks() : array + { + return $this->ecBlocks; + } +} diff --git a/src/Common/ErrorCorrectionLevel.php b/src/Common/ErrorCorrectionLevel.php new file mode 100644 index 0000000..9bbf440 --- /dev/null +++ b/src/Common/ErrorCorrectionLevel.php @@ -0,0 +1,63 @@ +bits = $bits; + } + + /** + * @throws OutOfBoundsException if number of bits is invalid + */ + public static function forBits(int $bits) : self + { + switch ($bits) { + case 0: + return self::M(); + + case 1: + return self::L(); + + case 2: + return self::H(); + + case 3: + return self::Q(); + } + + throw new OutOfBoundsException('Invalid number of bits'); + } + + /** + * Returns the two bits used to encode this error correction level. + */ + public function getBits() : int + { + return $this->bits; + } +} diff --git a/src/Common/FormatInformation.php b/src/Common/FormatInformation.php new file mode 100644 index 0000000..53e3541 --- /dev/null +++ b/src/Common/FormatInformation.php @@ -0,0 +1,203 @@ +ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3); + $this->dataMask = $formatInfo & 0x7; + } + + /** + * Checks how many bits are different between two integers. + */ + public static function numBitsDiffering(int $a, int $b) : int + { + $a ^= $b; + + return ( + self::BITS_SET_IN_HALF_BYTE[$a & 0xf] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)] + ); + } + + /** + * Decodes format information. + */ + public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self + { + $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); + + if (null !== $formatInfo) { + return $formatInfo; + } + + // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the + // pattern first. + return self::doDecodeFormatInformation( + $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, + $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR + ); + } + + /** + * Internal method for decoding format information. + */ + private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self + { + $bestDifference = PHP_INT_MAX; + $bestFormatInfo = 0; + + foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) { + $targetInfo = $decodeInfo[0]; + + if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { + // Found an exact match + return new self($decodeInfo[1]); + } + + $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); + + if ($bitsDifference < $bestDifference) { + $bestFormatInfo = $decodeInfo[1]; + $bestDifference = $bitsDifference; + } + + if ($maskedFormatInfo1 !== $maskedFormatInfo2) { + // Also try the other option + $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); + + if ($bitsDifference < $bestDifference) { + $bestFormatInfo = $decodeInfo[1]; + $bestDifference = $bitsDifference; + } + } + } + + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match. + if ($bestDifference <= 3) { + return new self($bestFormatInfo); + } + + return null; + } + + /** + * Returns the error correction level. + */ + public function getErrorCorrectionLevel() : ErrorCorrectionLevel + { + return $this->ecLevel; + } + + /** + * Returns the data mask. + */ + public function getDataMask() : int + { + return $this->dataMask; + } + + /** + * Hashes the code of the EC level. + */ + public function hashCode() : int + { + return ($this->ecLevel->getBits() << 3) | $this->dataMask; + } + + /** + * Verifies if this instance equals another one. + */ + public function equals(self $other) : bool + { + return ( + $this->ecLevel === $other->ecLevel + && $this->dataMask === $other->dataMask + ); + } +} diff --git a/src/Common/Mode.php b/src/Common/Mode.php new file mode 100644 index 0000000..51e6c9a --- /dev/null +++ b/src/Common/Mode.php @@ -0,0 +1,76 @@ +characterCountBitsForVersions = $characterCountBitsForVersions; + $this->bits = $bits; + } + + /** + * Returns the number of bits used in a specific QR code version. + */ + public function getCharacterCountBits(Version $version) : int + { + $number = $version->getVersionNumber(); + + if ($number <= 9) { + $offset = 0; + } elseif ($number <= 26) { + $offset = 1; + } else { + $offset = 2; + } + + return $this->characterCountBitsForVersions[$offset]; + } + + /** + * Returns the four bits used to encode this mode. + */ + public function getBits() : int + { + return $this->bits; + } +} diff --git a/src/BaconQrCode/Common/ReedSolomonCodec.php b/src/Common/ReedSolomonCodec.php similarity index 58% rename from src/BaconQrCode/Common/ReedSolomonCodec.php rename to src/Common/ReedSolomonCodec.php index e8d45b9..a5aad0b 100644 --- a/src/BaconQrCode/Common/ReedSolomonCodec.php +++ b/src/Common/ReedSolomonCodec.php @@ -1,15 +1,10 @@ 8) { - throw new Exception\InvalidArgumentException('Symbol size must be between 0 and 8'); + throw new InvalidArgumentException('Symbol size must be between 0 and 8'); } if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) { - throw new Exception\InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize)); + throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize)); } if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) { - throw new Exception\InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize)); + throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize)); } if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) { - throw new Exception\InvalidArgumentException('Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)); + throw new InvalidArgumentException( + 'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots) + ); } $this->symbolSize = $symbolSize; - $this->blockSize = (1 << $symbolSize) - 1; - $this->padding = $padding; - $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); - $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); + $this->blockSize = (1 << $symbolSize) - 1; + $this->padding = $padding; + $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); + $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); // Generate galous field lookup table - $this->indexOf[0] = $this->blockSize; + $this->indexOf[0] = $this->blockSize; $this->alphaTo[$this->blockSize] = 0; $sr = 1; - for ($i = 0; $i < $this->blockSize; $i++) { + for ($i = 0; $i < $this->blockSize; ++$i) { $this->indexOf[$sr] = $i; $this->alphaTo[$i] = $sr; @@ -144,28 +142,32 @@ public function __construct($symbolSize, $gfPoly, $firstRoot, $primitive, $numRo $sr &= $this->blockSize; } - if ($sr !== 1) { - throw new Exception\RuntimeException('Field generator polynomial is not primitive'); + if (1 !== $sr) { + throw new RuntimeException('Field generator polynomial is not primitive'); } // Form RS code generator polynomial from its roots $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false); - $this->firstRoot = $firstRoot; - $this->primitive = $primitive; - $this->numRoots = $numRoots; + $this->firstRoot = $firstRoot; + $this->primitive = $primitive; + $this->numRoots = $numRoots; // Find prim-th root of 1, used in decoding - for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize); - $this->iPrimitive = intval($iPrimitive / $primitive); + for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) { + } + + $this->iPrimitive = intdiv($iPrimitive, $primitive); $this->generatorPoly[0] = 1; - for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; $i++, $root += $primitive) { + for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) { $this->generatorPoly[$i + 1] = 1; for ($j = $i; $j > 0; $j--) { if ($this->generatorPoly[$j] !== 0) { - $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)]; + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[ + $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root) + ]; } else { $this->generatorPoly[$j] = $this->generatorPoly[$j - 1]; } @@ -175,39 +177,37 @@ public function __construct($symbolSize, $gfPoly, $firstRoot, $primitive, $numRo } // Convert generator poly to index form for quicker encoding - for ($i = 0; $i <= $numRoots; $i++) { + for ($i = 0; $i <= $numRoots; ++$i) { $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]]; } } /** * Encodes data and writes result back into parity array. - * - * @param SplFixedArray $data - * @param SplFixedArray $parity - * @return void */ - public function encode(SplFixedArray $data, SplFixedArray $parity) + public function encode(SplFixedArray $data, SplFixedArray $parity) : void { - for ($i = 0; $i < $this->numRoots; $i++) { + for ($i = 0; $i < $this->numRoots; ++$i) { $parity[$i] = 0; } $iterations = $this->blockSize - $this->numRoots - $this->padding; - for ($i = 0; $i < $iterations; $i++) { + for ($i = 0; $i < $iterations; ++$i) { $feedback = $this->indexOf[$data[$i] ^ $parity[0]]; if ($feedback !== $this->blockSize) { // Feedback term is non-zero $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback); - for ($j = 1; $j < $this->numRoots; $j++) { - $parity[$j] = $parity[$j] ^ $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])]; + for ($j = 1; $j < $this->numRoots; ++$j) { + $parity[$j] = $parity[$j] ^ $this->alphaTo[ + $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j]) + ]; } } - for ($j = 0; $j < $this->numRoots - 1; $j++) { + for ($j = 0; $j < $this->numRoots - 1; ++$j) { $parity[$j] = $parity[$j + 1]; } @@ -221,31 +221,27 @@ public function encode(SplFixedArray $data, SplFixedArray $parity) /** * Decodes received data. - * - * @param SplFixedArray $data - * @param SplFixedArray|null $erasures - * @return null|integer */ - public function decode(SplFixedArray $data, SplFixedArray $erasures = null) + public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int { // This speeds up the initialization a bit. $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false); - $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false); + $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false); - $lambda = clone $numRootsPlusOne; - $b = clone $numRootsPlusOne; - $t = clone $numRootsPlusOne; - $omega = clone $numRootsPlusOne; - $root = clone $numRoots; - $loc = clone $numRoots; + $lambda = clone $numRootsPlusOne; + $b = clone $numRootsPlusOne; + $t = clone $numRootsPlusOne; + $omega = clone $numRootsPlusOne; + $root = clone $numRoots; + $loc = clone $numRoots; - $numErasures = ($erasures !== null ? count($erasures) : 0); + $numErasures = (null !== $erasures ? count($erasures) : 0); // Form the Syndromes; i.e., evaluate data(x) at roots of g(x) $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false); - for ($i = 1; $i < $this->blockSize - $this->padding; $i++) { - for ($j = 0; $j < $this->numRoots; $j++) { + for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) { + for ($j = 0; $j < $this->numRoots; ++$j) { if ($syndromes[$j] === 0) { $syndromes[$j] = $data[$i]; } else { @@ -259,14 +255,14 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) // Convert syndromes to index form, checking for nonzero conditions $syndromeError = 0; - for ($i = 0; $i < $this->numRoots; $i++) { + for ($i = 0; $i < $this->numRoots; ++$i) { $syndromeError |= $syndromes[$i]; - $syndromes[$i] = $this->indexOf[$syndromes[$i]]; + $syndromes[$i] = $this->indexOf[$syndromes[$i]]; } - if (!$syndromeError) { - // If syndrome is zero, data[] is a codeword and there are no errors - // to correct, so return data[] unmodified. + if (! $syndromeError) { + // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[] + // unmodified. return 0; } @@ -276,10 +272,10 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) // Init lambda to be the erasure locator polynomial $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))]; - for ($i = 1; $i < $numErasures; $i++) { + for ($i = 1; $i < $numErasures; ++$i) { $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i])); - for ($j = $i + 1; $j > 0; $j--) { + for ($j = $i + 1; $j > 0; --$j) { $tmp = $this->indexOf[$lambda[$j - 1]]; if ($tmp !== $this->blockSize) { @@ -289,12 +285,11 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) } } - for ($i = 0; $i <= $this->numRoots; $i++) { + for ($i = 0; $i <= $this->numRoots; ++$i) { $b[$i] = $this->indexOf[$lambda[$i]]; } - // Begin Berlekamp-Massey algorithm to determine error+erasure locator - // polynomial + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial $r = $numErasures; $el = $numErasures; @@ -302,9 +297,11 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) // Compute discrepancy at the r-th step in poly form $discrepancyR = 0; - for ($i = 0; $i < $r; $i++) { + for ($i = 0; $i < $r; ++$i) { if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) { - $discrepancyR ^= $this->alphaTo[$this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])]; + $discrepancyR ^= $this->alphaTo[ + $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1]) + ]; } } @@ -315,42 +312,43 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) array_unshift($tmp, $this->blockSize); array_pop($tmp); $b = SplFixedArray::fromArray($tmp, false); - } else { - $t[0] = $lambda[0]; - - for ($i = 0; $i < $this->numRoots; $i++) { - if ($b[$i] !== $this->blockSize) { - $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])]; - } else { - $t[$i + 1] = $lambda[$i + 1]; - } - } + continue; + } - if (2 * $el <= $r + $numErasures - 1) { - $el = $r + $numErasures - $el; + $t[0] = $lambda[0]; - for ($i = 0; $i <= $this->numRoots; $i++) { - $b[$i] = ( - $lambda[$i] === 0 - ? $this->blockSize - : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize) - ); - } + for ($i = 0; $i < $this->numRoots; ++$i) { + if ($b[$i] !== $this->blockSize) { + $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])]; } else { - $tmp = $b->toArray(); - array_unshift($tmp, $this->blockSize); - array_pop($tmp); - $b = SplFixedArray::fromArray($tmp, false); + $t[$i + 1] = $lambda[$i + 1]; } + } + + if (2 * $el <= $r + $numErasures - 1) { + $el = $r + $numErasures - $el; - $lambda = clone $t; + for ($i = 0; $i <= $this->numRoots; ++$i) { + $b[$i] = ( + $lambda[$i] === 0 + ? $this->blockSize + : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize) + ); + } + } else { + $tmp = $b->toArray(); + array_unshift($tmp, $this->blockSize); + array_pop($tmp); + $b = SplFixedArray::fromArray($tmp, false); } + + $lambda = clone $t; } // Convert lambda to index form and compute deg(lambda(x)) $degLambda = 0; - for ($i = 0; $i <= $this->numRoots; $i++) { + for ($i = 0; $i <= $this->numRoots; ++$i) { $lambda[$i] = $this->indexOf[$lambda[$i]]; if ($lambda[$i] !== $this->blockSize) { @@ -359,17 +357,18 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) } // Find roots of the error+erasure locator polynomial by Chien search. - $reg = clone $lambda; + $reg = clone $lambda; $reg[0] = 0; - $count = 0; + $count = 0; + $i = 1; - for ($i = 1, $k = $this->iPrimitive - 1; $i <= $this->blockSize; $i++, $k = $this->modNn($k + $this->iPrimitive)) { + for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) { $q = 1; for ($j = $degLambda; $j > 0; $j--) { if ($reg[$j] !== $this->blockSize) { - $reg[$j] = $this->modNn($reg[$j] + $j); - $q ^= $this->alphaTo[$reg[$j]]; + $reg[$j] = $this->modNn($reg[$j] + $j); + $q ^= $this->alphaTo[$reg[$j]]; } } @@ -380,7 +379,7 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) // Store root (index-form) and error location number $root[$count] = $i; - $loc[$count] = $k; + $loc[$count] = $k; if (++$count === $degLambda) { break; @@ -388,19 +387,18 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) } if ($degLambda !== $count) { - // deg(lambda) unequal to number of roots: uncorreactable error - // detected + // deg(lambda) unequal to number of roots: uncorrectable error detected return null; } - // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo - // x**numRoots). In index form. Also find deg(omega). + // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find + // deg(omega). $degOmega = $degLambda - 1; - for ($i = 0; $i <= $degOmega; $i++) { + for ($i = 0; $i <= $degOmega; ++$i) { $tmp = 0; - for ($j = $i; $j >= 0; $j--) { + for ($j = $i; $j >= 0; --$j) { if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) { $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])]; } @@ -409,10 +407,9 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) $omega[$i] = $this->indexOf[$tmp]; } - // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = - // inv(X(l))**(firstRoot-1) and den = lambda_pr(inv(X(l))) all in poly - // form. - for ($j = $count - 1; $j >= 0; $j--) { + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and + // den = lambda_pr(inv(X(l))) all in poly form. + for ($j = $count - 1; $j >= 0; --$j) { $num1 = 0; for ($i = $degOmega; $i >= 0; $i--) { @@ -424,8 +421,7 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)]; $den = 0; - // lambda[i+1] for i even is the formal derivativelambda_pr of - // lambda[i] + // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i] for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) { if ($lambda[$i + 1] !== $this->blockSize) { $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])]; @@ -444,7 +440,7 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) } } - if ($erasures !== null) { + if (null !== $erasures) { if (count($erasures) < $count) { $erasures->setSize($count); } @@ -458,17 +454,13 @@ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) } /** - * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow - * divide. - * - * @param itneger $x - * @return integer + * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide. */ - protected function modNn($x) + private function modNn(int $x) : int { while ($x >= $this->blockSize) { $x -= $this->blockSize; - $x = ($x >> $this->symbolSize) + ($x & $this->blockSize); + $x = ($x >> $this->symbolSize) + ($x & $this->blockSize); } return $x; diff --git a/src/Common/Version.php b/src/Common/Version.php new file mode 100644 index 0000000..917d048 --- /dev/null +++ b/src/Common/Version.php @@ -0,0 +1,596 @@ +|null + */ + private static $versions; + + /** + * @param int[] $alignmentPatternCenters + */ + private function __construct( + int $versionNumber, + array $alignmentPatternCenters, + EcBlocks ...$ecBlocks + ) { + $this->versionNumber = $versionNumber; + $this->alignmentPatternCenters = $alignmentPatternCenters; + $this->ecBlocks = $ecBlocks; + + $totalCodewords = 0; + $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock(); + + foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) { + $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords); + } + + $this->totalCodewords = $totalCodewords; + } + + /** + * Returns the version number. + */ + public function getVersionNumber() : int + { + return $this->versionNumber; + } + + /** + * Returns the alignment pattern centers. + * + * @return int[] + */ + public function getAlignmentPatternCenters() : array + { + return $this->alignmentPatternCenters; + } + + /** + * Returns the total number of codewords. + */ + public function getTotalCodewords() : int + { + return $this->totalCodewords; + } + + /** + * Calculates the dimension for the current version. + */ + public function getDimensionForVersion() : int + { + return 17 + 4 * $this->versionNumber; + } + + /** + * Returns the number of EC blocks for a specific EC level. + */ + public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks + { + return $this->ecBlocks[$ecLevel->ordinal()]; + } + + /** + * Gets a provisional version number for a specific dimension. + * + * @throws InvalidArgumentException if dimension is not 1 mod 4 + */ + public static function getProvisionalVersionForDimension(int $dimension) : self + { + if (1 !== $dimension % 4) { + throw new InvalidArgumentException('Dimension is not 1 mod 4'); + } + + return self::getVersionForNumber(intdiv($dimension - 17, 4)); + } + + /** + * Gets a version instance for a specific version number. + * + * @throws InvalidArgumentException if version number is out of range + */ + public static function getVersionForNumber(int $versionNumber) : self + { + if ($versionNumber < 1 || $versionNumber > 40) { + throw new InvalidArgumentException('Version number must be between 1 and 40'); + } + + return self::versions()[$versionNumber - 1]; + } + + /** + * Decodes version information from an integer and returns the version. + */ + public static function decodeVersionInformation(int $versionBits) : ?self + { + $bestDifference = PHP_INT_MAX; + $bestVersion = 0; + + foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) { + if ($targetVersion === $versionBits) { + return self::getVersionForNumber($i + 7); + } + + $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion); + + if ($bitsDifference < $bestDifference) { + $bestVersion = $i + 7; + $bestDifference = $bitsDifference; + } + } + + if ($bestDifference <= 3) { + return self::getVersionForNumber($bestVersion); + } + + return null; + } + + /** + * Builds the function pattern for the current version. + */ + public function buildFunctionPattern() : BitMatrix + { + $dimension = $this->getDimensionForVersion(); + $bitMatrix = new BitMatrix($dimension); + + // Top left finder pattern + separator + format + $bitMatrix->setRegion(0, 0, 9, 9); + // Top right finder pattern + separator + format + $bitMatrix->setRegion($dimension - 8, 0, 8, 9); + // Bottom left finder pattern + separator + format + $bitMatrix->setRegion(0, $dimension - 8, 9, 8); + + // Alignment patterns + $max = count($this->alignmentPatternCenters); + + for ($x = 0; $x < $max; ++$x) { + $i = $this->alignmentPatternCenters[$x] - 2; + + for ($y = 0; $y < $max; ++$y) { + if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) { + // No alignment patterns near the three finder paterns + continue; + } + + $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5); + } + } + + // Vertical timing pattern + $bitMatrix->setRegion(6, 9, 1, $dimension - 17); + // Horizontal timing pattern + $bitMatrix->setRegion(9, 6, $dimension - 17, 1); + + if ($this->versionNumber > 6) { + // Version info, top right + $bitMatrix->setRegion($dimension - 11, 0, 3, 6); + // Version info, bottom left + $bitMatrix->setRegion(0, $dimension - 11, 6, 3); + } + + return $bitMatrix; + } + + /** + * Returns a string representation for the version. + */ + public function __toString() : string + { + return (string) $this->versionNumber; + } + + /** + * Build and cache a specific version. + * + * See ISO 18004:2006 6.5.1 Table 9. + * + * @return array + */ + private static function versions() : array + { + if (null !== self::$versions) { + return self::$versions; + } + + return self::$versions = [ + new self( + 1, + [], + new EcBlocks(7, new EcBlock(1, 19)), + new EcBlocks(10, new EcBlock(1, 16)), + new EcBlocks(13, new EcBlock(1, 13)), + new EcBlocks(17, new EcBlock(1, 9)) + ), + new self( + 2, + [6, 18], + new EcBlocks(10, new EcBlock(1, 34)), + new EcBlocks(16, new EcBlock(1, 28)), + new EcBlocks(22, new EcBlock(1, 22)), + new EcBlocks(28, new EcBlock(1, 16)) + ), + new self( + 3, + [6, 22], + new EcBlocks(15, new EcBlock(1, 55)), + new EcBlocks(26, new EcBlock(1, 44)), + new EcBlocks(18, new EcBlock(2, 17)), + new EcBlocks(22, new EcBlock(2, 13)) + ), + new self( + 4, + [6, 26], + new EcBlocks(20, new EcBlock(1, 80)), + new EcBlocks(18, new EcBlock(2, 32)), + new EcBlocks(26, new EcBlock(3, 24)), + new EcBlocks(16, new EcBlock(4, 9)) + ), + new self( + 5, + [6, 30], + new EcBlocks(26, new EcBlock(1, 108)), + new EcBlocks(24, new EcBlock(2, 43)), + new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)), + new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12)) + ), + new self( + 6, + [6, 34], + new EcBlocks(18, new EcBlock(2, 68)), + new EcBlocks(16, new EcBlock(4, 27)), + new EcBlocks(24, new EcBlock(4, 19)), + new EcBlocks(28, new EcBlock(4, 15)) + ), + new self( + 7, + [6, 22, 38], + new EcBlocks(20, new EcBlock(2, 78)), + new EcBlocks(18, new EcBlock(4, 31)), + new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)), + new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14)) + ), + new self( + 8, + [6, 24, 42], + new EcBlocks(24, new EcBlock(2, 97)), + new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)), + new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)), + new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15)) + ), + new self( + 9, + [6, 26, 46], + new EcBlocks(30, new EcBlock(2, 116)), + new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)), + new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)), + new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13)) + ), + new self( + 10, + [6, 28, 50], + new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)), + new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)), + new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)), + new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16)) + ), + new self( + 11, + [6, 30, 54], + new EcBlocks(20, new EcBlock(4, 81)), + new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)), + new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)), + new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13)) + ), + new self( + 12, + [6, 32, 58], + new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)), + new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)), + new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)), + new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15)) + ), + new self( + 13, + [6, 34, 62], + new EcBlocks(26, new EcBlock(4, 107)), + new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)), + new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)), + new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12)) + ), + new self( + 14, + [6, 26, 46, 66], + new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)), + new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)), + new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)), + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13)) + ), + new self( + 15, + [6, 26, 48, 70], + new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)), + new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)), + new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)), + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13)) + ), + new self( + 16, + [6, 26, 50, 74], + new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)), + new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)), + new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)), + new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16)) + ), + new self( + 17, + [6, 30, 54, 78], + new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)), + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)), + new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)), + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15)) + ), + new self( + 18, + [6, 30, 56, 82], + new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)), + new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)), + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)), + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15)) + ), + new self( + 19, + [6, 30, 58, 86], + new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)), + new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)), + new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)), + new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14)) + ), + new self( + 20, + [6, 34, 62, 90], + new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)), + new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)), + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)), + new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16)) + ), + new self( + 21, + [6, 28, 50, 72, 94], + new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)), + new EcBlocks(26, new EcBlock(17, 42)), + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)), + new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17)) + ), + new self( + 22, + [6, 26, 50, 74, 98], + new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)), + new EcBlocks(28, new EcBlock(17, 46)), + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)), + new EcBlocks(24, new EcBlock(34, 13)) + ), + new self( + 23, + [6, 30, 54, 78, 102], + new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)), + new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)), + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)), + new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16)) + ), + new self( + 24, + [6, 28, 54, 80, 106], + new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)), + new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)), + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)), + new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17)) + ), + new self( + 25, + [6, 32, 58, 84, 110], + new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)), + new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)), + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)), + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16)) + ), + new self( + 26, + [6, 30, 58, 86, 114], + new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)), + new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)), + new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)), + new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17)) + ), + new self( + 27, + [6, 34, 62, 90, 118], + new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)), + new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)), + new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)), + new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16)) + ), + new self( + 28, + [6, 26, 50, 74, 98, 122], + new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)), + new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)), + new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)), + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16)) + ), + new self( + 29, + [6, 30, 54, 78, 102, 126], + new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)), + new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)), + new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)), + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16)) + ), + new self( + 30, + [6, 26, 52, 78, 104, 130], + new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)), + new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)), + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)), + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16)) + ), + new self( + 31, + [6, 30, 56, 82, 108, 134], + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)), + new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)), + new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)), + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16)) + ), + new self( + 32, + [6, 34, 60, 86, 112, 138], + new EcBlocks(30, new EcBlock(17, 115)), + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)), + new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)), + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16)) + ), + new self( + 33, + [6, 30, 58, 86, 114, 142], + new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)), + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)), + new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)), + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16)) + ), + new self( + 34, + [6, 34, 62, 90, 118, 146], + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)), + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)), + new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)), + new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17)) + ), + new self( + 35, + [6, 30, 54, 78, 102, 126, 150], + new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)), + new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)), + new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)), + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16)) + ), + new self( + 36, + [6, 24, 50, 76, 102, 128, 154], + new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)), + new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)), + new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)), + new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16)) + ), + new self( + 37, + [6, 28, 54, 80, 106, 132, 158], + new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)), + new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)), + new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)), + new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16)) + ), + new self( + 38, + [6, 32, 58, 84, 110, 136, 162], + new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)), + new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)), + new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)), + new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16)) + ), + new self( + 39, + [6, 26, 54, 82, 110, 138, 166], + new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)), + new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)), + new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)), + new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16)) + ), + new self( + 40, + [6, 30, 58, 86, 114, 142, 170], + new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)), + new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)), + new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)), + new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16)) + ), + ]; + } +} diff --git a/src/BaconQrCode/Encoder/BlockPair.php b/src/Encoder/BlockPair.php similarity index 50% rename from src/BaconQrCode/Encoder/BlockPair.php rename to src/Encoder/BlockPair.php index 090db29..be54afa 100644 --- a/src/BaconQrCode/Encoder/BlockPair.php +++ b/src/Encoder/BlockPair.php @@ -1,11 +1,5 @@ */ - protected $dataBytes; + private $dataBytes; /** * Error correction bytes in the block. * - * @var SplFixedArray + * @var SplFixedArray */ - protected $errorCorrectionBytes; + private $errorCorrectionBytes; /** * Creates a new block pair. * - * @param SplFixedArray $data - * @param SplFixedArray $errorCorrection + * @param SplFixedArray $data + * @param SplFixedArray $errorCorrection */ public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection) { - $this->dataBytes = $data; + $this->dataBytes = $data; $this->errorCorrectionBytes = $errorCorrection; } /** * Gets the data bytes. * - * @return SplFixedArray + * @return SplFixedArray */ - public function getDataBytes() + public function getDataBytes() : SplFixedArray { return $this->dataBytes; } @@ -55,9 +49,9 @@ public function getDataBytes() /** * Gets the error correction bytes. * - * @return SplFixedArray + * @return SplFixedArray */ - public function getErrorCorrectionBytes() + public function getErrorCorrectionBytes() : SplFixedArray { return $this->errorCorrectionBytes; } diff --git a/src/BaconQrCode/Encoder/ByteMatrix.php b/src/Encoder/ByteMatrix.php similarity index 52% rename from src/BaconQrCode/Encoder/ByteMatrix.php rename to src/Encoder/ByteMatrix.php index a378f08..b58cc0a 100644 --- a/src/BaconQrCode/Encoder/ByteMatrix.php +++ b/src/Encoder/ByteMatrix.php @@ -1,75 +1,60 @@ > */ - protected $bytes; + private $bytes; /** * Width of the matrix. * - * @var integer + * @var int */ - protected $width; + private $width; /** * Height of the matrix. * - * @var integer + * @var int */ - protected $height; + private $height; - /** - * Creates a new byte matrix. - * - * @param integer $width - * @param integer $height - */ - public function __construct($width, $height) + public function __construct(int $width, int $height) { $this->height = $height; - $this->width = $width; - $this->bytes = new SplFixedArray($height); + $this->width = $width; + $this->bytes = new SplFixedArray($height); - for ($y = 0; $y < $height; $y++) { - $this->bytes[$y] = new SplFixedArray($width); + for ($y = 0; $y < $height; ++$y) { + $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0)); } } /** * Gets the width of the matrix. - * - * @return integer */ - public function getWidth() + public function getWidth() : int { return $this->width; } /** * Gets the height of the matrix. - * - * @return integer */ - public function getHeight() + public function getHeight() : int { return $this->height; } @@ -77,59 +62,66 @@ public function getHeight() /** * Gets the internal representation of the matrix. * - * @return SplFixedArray + * @return SplFixedArray> */ - public function getArray() + public function getArray() : SplFixedArray { return $this->bytes; } + /** + * @return Traversable + */ + public function getBytes() : Traversable + { + foreach ($this->bytes as $row) { + foreach ($row as $byte) { + yield $byte; + } + } + } + /** * Gets the byte for a specific position. - * - * @param integer $x - * @param integer $y - * @return integer */ - public function get($x, $y) + public function get(int $x, int $y) : int { return $this->bytes[$y][$x]; } /** * Sets the byte for a specific position. - * - * @param integer $x - * @param integer $y - * @param integer $value - * @return void */ - public function set($x, $y, $value) + public function set(int $x, int $y, int $value) : void { - $this->bytes[$y][$x] = (int) $value; + $this->bytes[$y][$x] = $value; } /** * Clears the matrix with a specific value. - * - * @param integer $value - * @return void */ - public function clear($value) + public function clear(int $value) : void { - for ($y = 0; $y < $this->height; $y++) { - for ($x = 0; $x < $this->width; $x++) { + for ($y = 0; $y < $this->height; ++$y) { + for ($x = 0; $x < $this->width; ++$x) { $this->bytes[$y][$x] = $value; } } } + public function __clone() + { + $this->bytes = clone $this->bytes; + + foreach ($this->bytes as $index => $row) { + $this->bytes[$index] = clone $row; + } + } + /** * Returns a string representation of the matrix. - * - * @return string */ - public function __toString() + public function __toString() : string { $result = ''; diff --git a/src/BaconQrCode/Encoder/Encoder.php b/src/Encoder/Encoder.php similarity index 52% rename from src/BaconQrCode/Encoder/Encoder.php rename to src/Encoder/Encoder.php index c8efc35..4345f57 100644 --- a/src/BaconQrCode/Encoder/Encoder.php +++ b/src/Encoder/Encoder.php @@ -1,11 +1,5 @@ get() === Mode::BYTE && $encoding !== self::DEFAULT_BYTE_MODE_ECODING) { + if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) { $eci = CharacterSetEci::getCharacterSetEciByName($encoding); - if ($eci !== null) { + if (null !== $eci) { self::appendEci($eci, $headerBits); } } @@ -89,27 +79,27 @@ public static function encode($content, ErrorCorrectionLevel $ecLevel, $encoding // But need to know how many bits it takes to know version. First we // take a guess at version by assuming version will be the minimum, 1: $provisionalBitsNeeded = $headerBits->getSize() - + $mode->getCharacterCountBits(Version::getVersionForNumber(1)) - + $dataBits->getSize(); + + $mode->getCharacterCountBits(Version::getVersionForNumber(1)) + + $dataBits->getSize(); $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel); // Use that guess to calculate the right version. I am still not sure // this works in 100% of cases. $bitsNeeded = $headerBits->getSize() - + $mode->getCharacterCountBits($provisionalVersion) - + $dataBits->getSize(); + + $mode->getCharacterCountBits($provisionalVersion) + + $dataBits->getSize(); $version = self::chooseVersion($bitsNeeded, $ecLevel); $headerAndDataBits = new BitArray(); $headerAndDataBits->appendBitArray($headerBits); // Find "length" of main segment and write it. - $numLetters = ($mode->get() === Mode::BYTE ? $dataBits->getSizeInBytes() : strlen($content)); + $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content)); self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits); // Put data together into the overall payload. $headerAndDataBits->appendBitArray($dataBits); - $ecBlocks = $version->getEcBlocksForLevel($ecLevel); + $ecBlocks = $version->getEcBlocksForLevel($ecLevel); $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords(); // Terminate the bits properly. @@ -123,36 +113,24 @@ public static function encode($content, ErrorCorrectionLevel $ecLevel, $encoding $ecBlocks->getNumBlocks() ); - $qrCode = new QrCode(); - $qrCode->setErrorCorrectionLevel($ecLevel); - $qrCode->setMode($mode); - $qrCode->setVersion($version); - - // Choose the mask pattern and set to "qrCode". - $dimension = $version->getDimensionForVersion(); - $matrix = new ByteMatrix($dimension, $dimension); + // Choose the mask pattern. + $dimension = $version->getDimensionForVersion(); + $matrix = new ByteMatrix($dimension, $dimension); $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix); - $qrCode->setMaskPattern($maskPattern); - // Build the matrix and set it to "qrCode". + // Build the matrix. MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix); - $qrCode->setMatrix($matrix); - return $qrCode; + return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix); } /** * Gets the alphanumeric code for a byte. - * - * @param string|integer $code - * @return integer */ - protected static function getAlphanumericCode($code) + private static function getAlphanumericCode(int $code) : int { - $code = (is_string($code) ? ord($code) : $code); - - if (isset(self::$alphanumericTable[$code])) { - return self::$alphanumericTable[$code]; + if (isset(self::ALPHANUMERIC_TABLE[$code])) { + return self::ALPHANUMERIC_TABLE[$code]; } return -1; @@ -160,49 +138,42 @@ protected static function getAlphanumericCode($code) /** * Chooses the best mode for a given content. - * - * @param string $content - * @param string $encoding - * @return Mode */ - protected static function chooseMode($content, $encoding = null) + private static function chooseMode(string $content, string $encoding = null) : Mode { - if (strcasecmp($encoding, 'SHIFT-JIS') === 0) { - return self::isOnlyDoubleByteKanji($content) ? new Mode(Mode::KANJI) : new Mode(Mode::BYTE); + if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) { + return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE(); } - $hasNumeric = false; + $hasNumeric = false; $hasAlphanumeric = false; - $contentLength = strlen($content); + $contentLength = strlen($content); - for ($i = 0; $i < $contentLength; $i++) { + for ($i = 0; $i < $contentLength; ++$i) { $char = $content[$i]; if (ctype_digit($char)) { $hasNumeric = true; - } elseif (self::getAlphanumericCode($char) !== -1) { + } elseif (-1 !== self::getAlphanumericCode(ord($char))) { $hasAlphanumeric = true; } else { - return new Mode(Mode::BYTE); + return Mode::BYTE(); } } if ($hasAlphanumeric) { - return new Mode(Mode::ALPHANUMERIC); + return Mode::ALPHANUMERIC(); } elseif ($hasNumeric) { - return new Mode(Mode::NUMERIC); + return Mode::NUMERIC(); } - return new Mode(Mode::BYTE); + return Mode::BYTE(); } /** * Calculates the mask penalty for a matrix. - * - * @param ByteMatrix $matrix - * @return integer */ - protected static function calculateMaskPenalty(ByteMatrix $matrix) + private static function calculateMaskPenalty(ByteMatrix $matrix) : int { return ( MaskUtil::applyMaskPenaltyRule1($matrix) @@ -212,30 +183,52 @@ protected static function calculateMaskPenalty(ByteMatrix $matrix) ); } + /** + * Checks if content only consists of double-byte kanji characters. + */ + private static function isOnlyDoubleByteKanji(string $content) : bool + { + $bytes = @iconv('utf-8', 'SHIFT-JIS', $content); + + if (false === $bytes) { + return false; + } + + $length = strlen($bytes); + + if (0 !== $length % 2) { + return false; + } + + for ($i = 0; $i < $length; $i += 2) { + $byte = $bytes[$i] & 0xff; + + if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) { + return false; + } + } + + return true; + } + /** * Chooses the best mask pattern for a matrix. - * - * @param BitArray $bits - * @param ErrorCorrectionLevel $ecLevel - * @param Version $version - * @param ByteMatrix $matrix - * @return integer */ - protected static function chooseMaskPattern( + private static function chooseMaskPattern( BitArray $bits, ErrorCorrectionLevel $ecLevel, Version $version, ByteMatrix $matrix - ) { - $minPenality = PHP_INT_MAX; + ) : int { + $minPenalty = PHP_INT_MAX; $bestMaskPattern = -1; - for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; $maskPattern++) { + for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) { MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix); $penalty = self::calculateMaskPenalty($matrix); - if ($penalty < $minPenality) { - $minPenality = $penalty; + if ($penalty < $minPenalty) { + $minPenalty = $penalty; $bestMaskPattern = $maskPattern; } } @@ -246,134 +239,130 @@ protected static function chooseMaskPattern( /** * Chooses the best version for the input. * - * @param integer $numInputBits - * @param ErrorCorrectionLevel $ecLevel - * @return Version - * @throws Exception\WriterException + * @throws WriterException if data is too big */ - protected static function chooseVersion($numInputBits, ErrorCorrectionLevel $ecLevel) + private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version { - for ($versionNum = 1; $versionNum <= 40; $versionNum++) { - $version = Version::getVersionForNumber($versionNum); + for ($versionNum = 1; $versionNum <= 40; ++$versionNum) { + $version = Version::getVersionForNumber($versionNum); $numBytes = $version->getTotalCodewords(); - $ecBlocks = $version->getEcBlocksForLevel($ecLevel); + $ecBlocks = $version->getEcBlocksForLevel($ecLevel); $numEcBytes = $ecBlocks->getTotalEcCodewords(); - $numDataBytes = $numBytes - $numEcBytes; - $totalInputBytes = intval(($numInputBits + 8) / 8); + $numDataBytes = $numBytes - $numEcBytes; + $totalInputBytes = intdiv($numInputBits + 8, 8); if ($numDataBytes >= $totalInputBytes) { return $version; } } - throw new Exception\WriterException('Data too big'); + throw new WriterException('Data too big'); } /** * Terminates the bits in a bit array. * - * @param integer $numDataBytes - * @param BitArray $bits - * @throws Exception\WriterException + * @throws WriterException if data bits cannot fit in the QR code + * @throws WriterException if bits size does not equal the capacity */ - protected static function terminateBits($numDataBytes, BitArray $bits) + private static function terminateBits(int $numDataBytes, BitArray $bits) : void { $capacity = $numDataBytes << 3; if ($bits->getSize() > $capacity) { - throw new Exception\WriterException('Data bits cannot fit in the QR code'); + throw new WriterException('Data bits cannot fit in the QR code'); } - for ($i = 0; $i < 4 && $bits->getSize() < $capacity; $i++) { + for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) { $bits->appendBit(false); } $numBitsInLastByte = $bits->getSize() & 0x7; if ($numBitsInLastByte > 0) { - for ($i = $numBitsInLastByte; $i < 8; $i++) { + for ($i = $numBitsInLastByte; $i < 8; ++$i) { $bits->appendBit(false); } } $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes(); - for ($i = 0; $i < $numPaddingBytes; $i++) { - $bits->appendBits(($i & 0x1) === 0 ? 0xec : 0x11, 8); + for ($i = 0; $i < $numPaddingBytes; ++$i) { + $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8); } if ($bits->getSize() !== $capacity) { - throw new Exception\WriterException('Bits size does not equal capacity'); + throw new WriterException('Bits size does not equal capacity'); } } /** * Gets number of data- and EC bytes for a block ID. * - * @param integer $numTotalBytes - * @param integer $numDataBytes - * @param integer $numRsBlocks - * @param integer $blockId - * @return array - * @throws Exception\WriterException + * @return int[] + * @throws WriterException if block ID is too large + * @throws WriterException if EC bytes mismatch + * @throws WriterException if RS blocks mismatch + * @throws WriterException if total bytes mismatch */ - protected static function getNumDataBytesAndNumEcBytesForBlockId( - $numTotalBytes, - $numDataBytes, - $numRsBlocks, - $blockId - ) { + private static function getNumDataBytesAndNumEcBytesForBlockId( + int $numTotalBytes, + int $numDataBytes, + int $numRsBlocks, + int $blockId + ) : array { if ($blockId >= $numRsBlocks) { - throw new Exception\WriterException('Block ID too large'); + throw new WriterException('Block ID too large'); } - $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks; - $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2; - $numTotalBytesInGroup1 = intval($numTotalBytes / $numRsBlocks); + $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks; + $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2; + $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks); $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1; - $numDataBytesInGroup1 = intval($numDataBytes / $numRsBlocks); - $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1; - $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1; - $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2; + $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks); + $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1; + $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1; + $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2; if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) { - throw new Exception\WriterException('EC bytes mismatch'); + throw new WriterException('EC bytes mismatch'); } if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) { - throw new Exception\WriterException('RS blocks mismatch'); + throw new WriterException('RS blocks mismatch'); } if ($numTotalBytes !== (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1) + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2) ) { - throw new Exception\WriterException('Total bytes mismatch'); + throw new WriterException('Total bytes mismatch'); } if ($blockId < $numRsBlocksInGroup1) { - return array($numDataBytesInGroup1, $numEcBytesInGroup1); + return [$numDataBytesInGroup1, $numEcBytesInGroup1]; } else { - return array($numDataBytesInGroup2, $numEcBytesInGroup2); + return [$numDataBytesInGroup2, $numEcBytesInGroup2]; } } /** * Interleaves data with EC bytes. * - * @param BitArray $bits - * @param integer $numTotalBytes - * @param integer $numDataBytes - * @param integer $numRsBlocks - * @return BitArray - * @throws Exception\WriterException + * @throws WriterException if number of bits and data bytes does not match + * @throws WriterException if data bytes does not match offset + * @throws WriterException if an interleaving error occurs */ - protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, $numDataBytes, $numRsBlocks) - { + private static function interleaveWithEcBytes( + BitArray $bits, + int $numTotalBytes, + int $numDataBytes, + int $numRsBlocks + ) : BitArray { if ($bits->getSizeInBytes() !== $numDataBytes) { - throw new Exception\WriterException('Number of bits and data bytes does not match'); + throw new WriterException('Number of bits and data bytes does not match'); } $dataBytesOffset = 0; @@ -382,7 +371,7 @@ protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, $blocks = new SplFixedArray($numRsBlocks); - for ($i = 0; $i < $numRsBlocks; $i++) { + for ($i = 0; $i < $numRsBlocks; ++$i) { list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId( $numTotalBytes, $numDataBytes, @@ -390,23 +379,23 @@ protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, $i ); - $size = $numDataBytesInBlock; - $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size); - $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock); + $size = $numDataBytesInBlock; + $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size); + $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock); $blocks[$i] = new BlockPair($dataBytes, $ecBytes); - $maxNumDataBytes = max($maxNumDataBytes, $size); - $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes)); + $maxNumDataBytes = max($maxNumDataBytes, $size); + $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes)); $dataBytesOffset += $numDataBytesInBlock; } if ($numDataBytes !== $dataBytesOffset) { - throw new Exception\WriterException('Data bytes does not match offset'); + throw new WriterException('Data bytes does not match offset'); } $result = new BitArray(); - for ($i = 0; $i < $maxNumDataBytes; $i++) { + for ($i = 0; $i < $maxNumDataBytes; ++$i) { foreach ($blocks as $block) { $dataBytes = $block->getDataBytes(); @@ -416,7 +405,7 @@ protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, } } - for ($i = 0; $i < $maxNumEcBytes; $i++) { + for ($i = 0; $i < $maxNumEcBytes; ++$i) { foreach ($blocks as $block) { $ecBytes = $block->getErrorCorrectionBytes(); @@ -427,7 +416,9 @@ protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, } if ($numTotalBytes !== $result->getSizeInBytes()) { - throw new Exception\WriterException('Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'); + throw new WriterException( + 'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ' + ); } return $result; @@ -436,21 +427,20 @@ protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, /** * Generates EC bytes for given data. * - * @param SplFixedArray $dataBytes - * @param integer $numEcBytesInBlock - * @return SplFixedArray + * @param SplFixedArray $dataBytes + * @return SplFixedArray */ - protected static function generateEcBytes(SplFixedArray $dataBytes, $numEcBytesInBlock) + private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray { $numDataBytes = count($dataBytes); - $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock); + $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock); for ($i = 0; $i < $numDataBytes; $i++) { $toEncode[$i] = $dataBytes[$i] & 0xff; } $ecBytes = new SplFixedArray($numEcBytesInBlock); - $codec = self::getCodec($numDataBytes, $numEcBytesInBlock); + $codec = self::getCodec($numDataBytes, $numEcBytesInBlock); $codec->encode($toEncode, $ecBytes); return $ecBytes; @@ -458,57 +448,44 @@ protected static function generateEcBytes(SplFixedArray $dataBytes, $numEcBytesI /** * Gets an RS codec and caches it. - * - * @param integer $numDataBytes - * @param integer $numEcBytesInBlock - * @return ReedSolomonCodec */ - protected static function getCodec($numDataBytes, $numEcBytesInBlock) + private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec { $cacheId = $numDataBytes . '-' . $numEcBytesInBlock; - if (!isset(self::$codecs[$cacheId])) { - self::$codecs[$cacheId] = new ReedSolomonCodec( - 8, - 0x11d, - 0, - 1, - $numEcBytesInBlock, - 255 - $numDataBytes - $numEcBytesInBlock - ); + if (isset(self::$codecs[$cacheId])) { + return self::$codecs[$cacheId]; } - return self::$codecs[$cacheId]; + return self::$codecs[$cacheId] = new ReedSolomonCodec( + 8, + 0x11d, + 0, + 1, + $numEcBytesInBlock, + 255 - $numDataBytes - $numEcBytesInBlock + ); } /** * Appends mode information to a bit array. - * - * @param Mode $mode - * @param BitArray $bits - * @return void */ - protected static function appendModeInfo(Mode $mode, BitArray $bits) + private static function appendModeInfo(Mode $mode, BitArray $bits) : void { - $bits->appendBits($mode->get(), 4); + $bits->appendBits($mode->getBits(), 4); } /** * Appends length information to a bit array. * - * @param integer $numLetters - * @param Version $version - * @param Mode $mode - * @param BitArray $bits - * @return void - * @throws Exception\WriterException + * @throws WriterException if num letters is bigger than expected */ - protected static function appendLengthInfo($numLetters, Version $version, Mode $mode, BitArray $bits) + private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void { $numBits = $mode->getCharacterCountBits($version); if ($numLetters >= (1 << $numBits)) { - throw new Exception\WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1)); + throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1)); } $bits->appendBits($numLetters, $numBits); @@ -517,48 +494,39 @@ protected static function appendLengthInfo($numLetters, Version $version, Mode $ /** * Appends bytes to a bit array in a specific mode. * - * @param stirng $content - * @param Mode $mode - * @param BitArray $bits - * @param string $encoding - * @return void - * @throws Exception\WriterException + * @throws WriterException if an invalid mode was supplied */ - protected static function appendBytes($content, Mode $mode, BitArray $bits, $encoding) + private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void { - switch ($mode->get()) { - case Mode::NUMERIC: + switch ($mode) { + case Mode::NUMERIC(): self::appendNumericBytes($content, $bits); break; - case Mode::ALPHANUMERIC: + case Mode::ALPHANUMERIC(): self::appendAlphanumericBytes($content, $bits); break; - case Mode::BYTE: + case Mode::BYTE(): self::append8BitBytes($content, $bits, $encoding); break; - case Mode::KANJI: + case Mode::KANJI(): self::appendKanjiBytes($content, $bits); break; default: - throw new Exception\WriterException('Invalid mode: ' . $mode->get()); + throw new WriterException('Invalid mode: ' . $mode); } } /** * Appends numeric bytes to a bit array. - * - * @param string $content - * @param BitArray $bits - * @return void */ - protected static function appendNumericBytes($content, BitArray $bits) + private static function appendNumericBytes(string $content, BitArray $bits) : void { $length = strlen($content); - $i = 0; + $i = 0; while ($i < $length) { $num1 = (int) $content[$i]; @@ -577,7 +545,7 @@ protected static function appendNumericBytes($content, BitArray $bits) } else { // Encode one numeric letter in four bits. $bits->appendBits($num1, 4); - $i++; + ++$i; } } } @@ -585,23 +553,25 @@ protected static function appendNumericBytes($content, BitArray $bits) /** * Appends alpha-numeric bytes to a bit array. * - * @param string $content - * @param BitArray $bits - * @return void + * @throws WriterException if an invalid alphanumeric code was found */ - protected static function appendAlphanumericBytes($content, BitArray $bits) + private static function appendAlphanumericBytes(string $content, BitArray $bits) : void { $length = strlen($content); - $i = 0; + $i = 0; while ($i < $length) { - if (-1 === ($code1 = self::getAlphanumericCode($content[$i]))) { - throw new Exception\WriterException('Invalid alphanumeric code'); + $code1 = self::getAlphanumericCode(ord($content[$i])); + + if (-1 === $code1) { + throw new WriterException('Invalid alphanumeric code'); } if ($i + 1 < $length) { - if (-1 === ($code2 = self::getAlphanumericCode($content[$i + 1]))) { - throw new Exception\WriterException('Invalid alphanumeric code'); + $code2 = self::getAlphanumericCode(ord($content[$i + 1])); + + if (-1 === $code2) { + throw new WriterException('Invalid alphanumeric code'); } // Encode two alphanumeric letters in 11 bits. @@ -610,7 +580,7 @@ protected static function appendAlphanumericBytes($content, BitArray $bits) } else { // Encode one alphanumeric letter in six bits. $bits->appendBits($code1, 6); - $i++; + ++$i; } } } @@ -618,14 +588,14 @@ protected static function appendAlphanumericBytes($content, BitArray $bits) /** * Appends regular 8-bit bytes to a bit array. * - * @param string $content - * @param BitArray $bits - * @return void + * @throws WriterException if content cannot be encoded to target encoding */ - protected static function append8BitBytes($content, BitArray $bits, $encoding) + private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void { - if (false === ($bytes = @iconv('utf-8', $encoding, $content))) { - throw new Exception\WriterException('Could not encode content to ' . $encoding); + $bytes = @iconv('utf-8', $encoding, $content); + + if (false === $bytes) { + throw new WriterException('Could not encode content to ' . $encoding); } $length = strlen($bytes); @@ -638,16 +608,15 @@ protected static function append8BitBytes($content, BitArray $bits, $encoding) /** * Appends KANJI bytes to a bit array. * - * @param string $content - * @param BitArray $bits - * @return void + * @throws WriterException if content does not seem to be encoded in SHIFT-JIS + * @throws WriterException if an invalid byte sequence occurs */ - protected static function appendKanjiBytes($content, BitArray $bits) + private static function appendKanjiBytes(string $content, BitArray $bits) : void { if (strlen($content) % 2 > 0) { // We just do a simple length check here. The for loop will check // individual characters. - throw new Exception\WriterException('Content does not seem to be encoded in SHIFT-JIS'); + throw new WriterException('Content does not seem to be encoded in SHIFT-JIS'); } $length = strlen($content); @@ -655,14 +624,14 @@ protected static function appendKanjiBytes($content, BitArray $bits) for ($i = 0; $i < $length; $i += 2) { $byte1 = ord($content[$i]) & 0xff; $byte2 = ord($content[$i + 1]) & 0xff; - $code = ($byte1 << 8) | $byte2; + $code = ($byte1 << 8) | $byte2; if ($code >= 0x8140 && $code <= 0x9ffc) { $subtracted = $code - 0x8140; } elseif ($code >= 0xe040 && $code <= 0xebbf) { $subtracted = $code - 0xc140; } else { - throw new Exception\WriterException('Invalid byte sequence'); + throw new WriterException('Invalid byte sequence'); } $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff); @@ -673,15 +642,11 @@ protected static function appendKanjiBytes($content, BitArray $bits) /** * Appends ECI information to a bit array. - * - * @param CharacterSetEci $eci - * @param BitArray $bits - * @return void */ - protected static function appendEci(CharacterSetEci $eci, BitArray $bits) + private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void { - $mode = new Mode(Mode::ECI); - $bits->appendBits($mode->get(), 4); - $bits->appendBits($eci->get(), 8); + $mode = Mode::ECI(); + $bits->appendBits($mode->getBits(), 4); + $bits->appendBits($eci->getValue(), 8); } } diff --git a/src/BaconQrCode/Encoder/MaskUtil.php b/src/Encoder/MaskUtil.php similarity index 53% rename from src/BaconQrCode/Encoder/MaskUtil.php rename to src/Encoder/MaskUtil.php index c294d55..3baddbd 100644 --- a/src/BaconQrCode/Encoder/MaskUtil.php +++ b/src/Encoder/MaskUtil.php @@ -1,20 +1,15 @@ getArray(); - $width = $matrix->getWidth(); - $height = $matrix->getHeight(); + $array = $matrix->getArray(); + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); - for ($y = 0; $y < $height - 1; $y++) { - for ($x = 0; $x < $width - 1; $x++) { + for ($y = 0; $y < $height - 1; ++$y) { + for ($x = 0; $x < $width - 1; ++$x) { $value = $array[$y][$x]; - if ($value === $array[$y][$x + 1] && $value === $array[$y + 1][$x] && $value === $array[$y + 1][$x + 1]) { - $penalty++; + if ($value === $array[$y][$x + 1] + && $value === $array[$y + 1][$x] + && $value === $array[$y + 1][$x + 1] + ) { + ++$penalty; } } } @@ -79,71 +75,66 @@ public static function applyMaskPenaltyRule2(ByteMatrix $matrix) * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty * to them. If we find patterns like 000010111010000, we give penalties * twice (i.e. 40 * 2). - * - * @param ByteMatrix $matrix - * @return integer */ - public static function applyMaskPenaltyRule3(ByteMatrix $matrix) + public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int { $penalty = 0; - $array = $matrix->getArray(); - $width = $matrix->getWidth(); - $height = $matrix->getHeight(); - - for ($y = 0; $y < $height; $y++) { - for ($x = 0; $x < $width; $x++) { - if ( - $x + 6 < $width - && $array[$y][$x] === 1 - && $array[$y][$x + 1] === 0 - && $array[$y][$x + 2] === 1 - && $array[$y][$x + 3] === 1 - && $array[$y][$x + 4] === 1 - && $array[$y][$x + 5] === 0 - && $array[$y][$x + 6] === 1 + $array = $matrix->getArray(); + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); + + for ($y = 0; $y < $height; ++$y) { + for ($x = 0; $x < $width; ++$x) { + if ($x + 6 < $width + && 1 === $array[$y][$x] + && 0 === $array[$y][$x + 1] + && 1 === $array[$y][$x + 2] + && 1 === $array[$y][$x + 3] + && 1 === $array[$y][$x + 4] + && 0 === $array[$y][$x + 5] + && 1 === $array[$y][$x + 6] && ( ( $x + 10 < $width - && $array[$y][$x + 7] === 0 - && $array[$y][$x + 8] === 0 - && $array[$y][$x + 9] === 0 - && $array[$y][$x + 10] === 0 + && 0 === $array[$y][$x + 7] + && 0 === $array[$y][$x + 8] + && 0 === $array[$y][$x + 9] + && 0 === $array[$y][$x + 10] ) || ( $x - 4 >= 0 - && $array[$y][$x - 1] === 0 - && $array[$y][$x - 2] === 0 - && $array[$y][$x - 3] === 0 - && $array[$y][$x - 4] === 0 + && 0 === $array[$y][$x - 1] + && 0 === $array[$y][$x - 2] + && 0 === $array[$y][$x - 3] + && 0 === $array[$y][$x - 4] ) ) ) { $penalty += self::N3; } - if ( - $y + 6 < $height - && $array[$y][$x] === 1 - && $array[$y + 1][$x] === 0 - && $array[$y + 2][$x] === 1 - && $array[$y + 3][$x] === 1 - && $array[$y + 4][$x] === 1 - && $array[$y + 5][$x] === 0 - && $array[$y + 6][$x] === 1 + if ($y + 6 < $height + && 1 === $array[$y][$x] + && 0 === $array[$y + 1][$x] + && 1 === $array[$y + 2][$x] + && 1 === $array[$y + 3][$x] + && 1 === $array[$y + 4][$x] + && 0 === $array[$y + 5][$x] + && 1 === $array[$y + 6][$x] && ( ( $y + 10 < $height - && $array[$y + 7][$x] === 0 - && $array[$y + 8][$x] === 0 - && $array[$y + 9][$x] === 0 - && $array[$y + 10][$x] === 0 + && 0 === $array[$y + 7][$x] + && 0 === $array[$y + 8][$x] + && 0 === $array[$y + 9][$x] + && 0 === $array[$y + 10][$x] ) || ( $y - 4 >= 0 - && $array[$y - 1][$x] === 0 - && $array[$y - 2][$x] === 0 - && $array[$y - 3][$x] === 0 - && $array[$y - 4][$x] === 0 + && 0 === $array[$y - 1][$x] + && 0 === $array[$y - 2][$x] + && 0 === $array[$y - 3][$x] + && 0 === $array[$y - 4][$x] ) ) ) { @@ -160,30 +151,27 @@ public static function applyMaskPenaltyRule3(ByteMatrix $matrix) * * Calculates the ratio of dark cells and gives penalty if the ratio is far * from 50%. It gives 10 penalty for 5% distance. - * - * @param ByteMatrix $matrix - * @return integer */ - public static function applyMaskPenaltyRule4(ByteMatrix $matrix) + public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int { $numDarkCells = 0; - $array = $matrix->getArray(); - $width = $matrix->getWidth(); + $array = $matrix->getArray(); + $width = $matrix->getWidth(); $height = $matrix->getHeight(); - for ($y = 0; $y < $height; $y++) { + for ($y = 0; $y < $height; ++$y) { $arrayY = $array[$y]; - for ($x = 0; $x < $width; $x++) { - if ($arrayY[$x] === 1) { - $numDarkCells++; + for ($x = 0; $x < $width; ++$x) { + if (1 === $arrayY[$x]) { + ++$numDarkCells; } } } - $numTotalCells = $height * $width; - $darkRatio = $numDarkCells / $numTotalCells; + $numTotalCells = $height * $width; + $darkRatio = $numDarkCells / $numTotalCells; $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20); return $fixedPercentVariances * self::N4; @@ -194,13 +182,9 @@ public static function applyMaskPenaltyRule4(ByteMatrix $matrix) * * See 8.8 of JISX0510:2004 for mask pattern conditions. * - * @param integer $maskPattern - * @param integer $x - * @param integer $y - * @return integer - * @throws Exception\InvalidArgumentException + * @throws InvalidArgumentException if an invalid mask pattern was supplied */ - public static function getDataMaskBit($maskPattern, $x, $y) + public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool { switch ($maskPattern) { case 0: @@ -224,25 +208,25 @@ public static function getDataMaskBit($maskPattern, $x, $y) break; case 5: - $temp = $y * $x; + $temp = $y * $x; $intermediate = ($temp & 0x1) + ($temp % 3); break; case 6: - $temp = $y * $x; + $temp = $y * $x; $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1; break; case 7: - $temp = $y * $x; + $temp = $y * $x; $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1; break; default: - throw new Exception\InvalidArgumentException('Invalid mask pattern: ' . $maskPattern); + throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern); } - return $intermediate === 0; + return 0 == $intermediate; } /** @@ -250,34 +234,30 @@ public static function getDataMaskBit($maskPattern, $x, $y) * * We need this for doing this calculation in both vertical and horizontal * orders respectively. - * - * @param ByteMatrix $matrix - * @param boolean $isHorizontal - * @return integer */ - protected static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, $isHorizontal) + private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int { $penalty = 0; - $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth(); - $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight(); - $array = $matrix->getArray(); + $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth(); + $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight(); + $array = $matrix->getArray(); - for ($i = 0; $i < $iLimit; $i++) { + for ($i = 0; $i < $iLimit; ++$i) { $numSameBitCells = 0; - $prevBit = -1; + $prevBit = -1; for ($j = 0; $j < $jLimit; $j++) { $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i]; if ($bit === $prevBit) { - $numSameBitCells++; + ++$numSameBitCells; } else { if ($numSameBitCells >= 5) { $penalty += self::N1 + ($numSameBitCells - 5); } $numSameBitCells = 1; - $prevBit = $bit; + $prevBit = $bit; } } diff --git a/src/Encoder/MatrixUtil.php b/src/Encoder/MatrixUtil.php new file mode 100644 index 0000000..0967e29 --- /dev/null +++ b/src/Encoder/MatrixUtil.php @@ -0,0 +1,513 @@ +clear(-1); + } + + /** + * Builds a complete matrix. + */ + public static function buildMatrix( + BitArray $dataBits, + ErrorCorrectionLevel $level, + Version $version, + int $maskPattern, + ByteMatrix $matrix + ) : void { + self::clearMatrix($matrix); + self::embedBasicPatterns($version, $matrix); + self::embedTypeInfo($level, $maskPattern, $matrix); + self::maybeEmbedVersionInfo($version, $matrix); + self::embedDataBits($dataBits, $maskPattern, $matrix); + } + + /** + * Removes the position detection patterns from a matrix. + * + * This can be useful if you need to render those patterns separately. + */ + public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void + { + $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]); + + self::removePositionDetectionPattern(0, 0, $matrix); + self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); + self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); + } + + /** + * Embeds type information into a matrix. + */ + private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void + { + $typeInfoBits = new BitArray(); + self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits); + + $typeInfoBitsSize = $typeInfoBits->getSize(); + + for ($i = 0; $i < $typeInfoBitsSize; ++$i) { + $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i); + + $x1 = self::TYPE_INFO_COORDINATES[$i][0]; + $y1 = self::TYPE_INFO_COORDINATES[$i][1]; + + $matrix->set($x1, $y1, (int) $bit); + + if ($i < 8) { + $x2 = $matrix->getWidth() - $i - 1; + $y2 = 8; + } else { + $x2 = 8; + $y2 = $matrix->getHeight() - 7 + ($i - 8); + } + + $matrix->set($x2, $y2, (int) $bit); + } + } + + /** + * Generates type information bits and appends them to a bit array. + * + * @throws RuntimeException if bit array resulted in invalid size + */ + private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void + { + $typeInfo = ($level->getBits() << 3) | $maskPattern; + $bits->appendBits($typeInfo, 5); + + $bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY); + $bits->appendBits($bchCode, 10); + + $maskBits = new BitArray(); + $maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15); + $bits->xorBits($maskBits); + + if (15 !== $bits->getSize()) { + throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); + } + } + + /** + * Embeds version information if required. + */ + private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void + { + if ($version->getVersionNumber() < 7) { + return; + } + + $versionInfoBits = new BitArray(); + self::makeVersionInfoBits($version, $versionInfoBits); + + $bitIndex = 6 * 3 - 1; + + for ($i = 0; $i < 6; ++$i) { + for ($j = 0; $j < 3; ++$j) { + $bit = $versionInfoBits->get($bitIndex); + --$bitIndex; + + $matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit); + $matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit); + } + } + } + + /** + * Generates version information bits and appends them to a bit array. + * + * @throws RuntimeException if bit array resulted in invalid size + */ + private static function makeVersionInfoBits(Version $version, BitArray $bits) : void + { + $bits->appendBits($version->getVersionNumber(), 6); + + $bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY); + $bits->appendBits($bchCode, 12); + + if (18 !== $bits->getSize()) { + throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); + } + } + + /** + * Calculates the BCH code for a value and a polynomial. + */ + private static function calculateBchCode(int $value, int $poly) : int + { + $msbSetInPoly = self::findMsbSet($poly); + $value <<= $msbSetInPoly - 1; + + while (self::findMsbSet($value) >= $msbSetInPoly) { + $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly); + } + + return $value; + } + + /** + * Finds and MSB set. + */ + private static function findMsbSet(int $value) : int + { + $numDigits = 0; + + while (0 !== $value) { + $value >>= 1; + ++$numDigits; + } + + return $numDigits; + } + + /** + * Embeds basic patterns into a matrix. + */ + private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void + { + self::embedPositionDetectionPatternsAndSeparators($matrix); + self::embedDarkDotAtLeftBottomCorner($matrix); + self::maybeEmbedPositionAdjustmentPatterns($version, $matrix); + self::embedTimingPatterns($matrix); + } + + /** + * Embeds position detection patterns and separators into a byte matrix. + */ + private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void + { + $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]); + + self::embedPositionDetectionPattern(0, 0, $matrix); + self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); + self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); + + $hspWidth = 8; + + self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix); + self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix); + self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix); + + $vspSize = 7; + + self::embedVerticalSeparationPattern($vspSize, 0, $matrix); + self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix); + self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix); + } + + /** + * Embeds a single position detection pattern into a byte matrix. + */ + private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 7; ++$y) { + for ($x = 0; $x < 7; ++$x) { + $matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]); + } + } + } + + private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 7; ++$y) { + for ($x = 0; $x < 7; ++$x) { + $matrix->set($xStart + $x, $yStart + $y, 0); + } + } + } + + /** + * Embeds a single horizontal separation pattern. + * + * @throws RuntimeException if a byte was already set + */ + private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($x = 0; $x < 8; $x++) { + if (-1 !== $matrix->get($xStart + $x, $yStart)) { + throw new RuntimeException('Byte already set'); + } + + $matrix->set($xStart + $x, $yStart, 0); + } + } + + /** + * Embeds a single vertical separation pattern. + * + * @throws RuntimeException if a byte was already set + */ + private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 7; $y++) { + if (-1 !== $matrix->get($xStart, $yStart + $y)) { + throw new RuntimeException('Byte already set'); + } + + $matrix->set($xStart, $yStart + $y, 0); + } + } + + /** + * Embeds a dot at the left bottom corner. + * + * @throws RuntimeException if a byte was already set to 0 + */ + private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void + { + if (0 === $matrix->get(8, $matrix->getHeight() - 8)) { + throw new RuntimeException('Byte already set to 0'); + } + + $matrix->set(8, $matrix->getHeight() - 8, 1); + } + + /** + * Embeds position adjustment patterns if required. + */ + private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void + { + if ($version->getVersionNumber() < 2) { + return; + } + + $index = $version->getVersionNumber() - 1; + + $coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index]; + $numCoordinates = count($coordinates); + + for ($i = 0; $i < $numCoordinates; ++$i) { + for ($j = 0; $j < $numCoordinates; ++$j) { + $y = $coordinates[$i]; + $x = $coordinates[$j]; + + if (null === $x || null === $y) { + continue; + } + + if (-1 === $matrix->get($x, $y)) { + self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix); + } + } + } + } + + /** + * Embeds a single position adjustment pattern. + */ + private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 5; $y++) { + for ($x = 0; $x < 5; $x++) { + $matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]); + } + } + } + + /** + * Embeds timing patterns into a matrix. + */ + private static function embedTimingPatterns(ByteMatrix $matrix) : void + { + $matrixWidth = $matrix->getWidth(); + + for ($i = 8; $i < $matrixWidth - 8; ++$i) { + $bit = ($i + 1) % 2; + + if (-1 === $matrix->get($i, 6)) { + $matrix->set($i, 6, $bit); + } + + if (-1 === $matrix->get(6, $i)) { + $matrix->set(6, $i, $bit); + } + } + } + + /** + * Embeds "dataBits" using "getMaskPattern". + * + * For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for + * how to embed data bits. + * + * @throws WriterException if not all bits could be consumed + */ + private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void + { + $bitIndex = 0; + $direction = -1; + + // Start from the right bottom cell. + $x = $matrix->getWidth() - 1; + $y = $matrix->getHeight() - 1; + + while ($x > 0) { + // Skip vertical timing pattern. + if (6 === $x) { + --$x; + } + + while ($y >= 0 && $y < $matrix->getHeight()) { + for ($i = 0; $i < 2; $i++) { + $xx = $x - $i; + + // Skip the cell if it's not empty. + if (-1 !== $matrix->get($xx, $y)) { + continue; + } + + if ($bitIndex < $dataBits->getSize()) { + $bit = $dataBits->get($bitIndex); + ++$bitIndex; + } else { + // Padding bit. If there is no bit left, we'll fill the + // left cells with 0, as described in 8.4.9 of + // JISX0510:2004 (p. 24). + $bit = false; + } + + // Skip masking if maskPattern is -1. + if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) { + $bit = ! $bit; + } + + $matrix->set($xx, $y, (int) $bit); + } + + $y += $direction; + } + + $direction = -$direction; + $y += $direction; + $x -= 2; + } + + // All bits should be consumed + if ($dataBits->getSize() !== $bitIndex) { + throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')'); + } + } +} diff --git a/src/Encoder/QrCode.php b/src/Encoder/QrCode.php new file mode 100644 index 0000000..f568e88 --- /dev/null +++ b/src/Encoder/QrCode.php @@ -0,0 +1,141 @@ +mode = $mode; + $this->errorCorrectionLevel = $errorCorrectionLevel; + $this->version = $version; + $this->maskPattern = $maskPattern; + $this->matrix = $matrix; + } + + /** + * Gets the mode. + */ + public function getMode() : Mode + { + return $this->mode; + } + + /** + * Gets the EC level. + */ + public function getErrorCorrectionLevel() : ErrorCorrectionLevel + { + return $this->errorCorrectionLevel; + } + + /** + * Gets the version. + */ + public function getVersion() : Version + { + return $this->version; + } + + /** + * Gets the mask pattern. + */ + public function getMaskPattern() : int + { + return $this->maskPattern; + } + + /** + * Gets the matrix. + * + * @return ByteMatrix + */ + public function getMatrix() + { + return $this->matrix; + } + + /** + * Validates whether a mask pattern is valid. + */ + public static function isValidMaskPattern(int $maskPattern) : bool + { + return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS; + } + + /** + * Returns a string representation of the QR code. + */ + public function __toString() : string + { + $result = "<<\n" + . ' mode: ' . $this->mode . "\n" + . ' ecLevel: ' . $this->errorCorrectionLevel . "\n" + . ' version: ' . $this->version . "\n" + . ' maskPattern: ' . $this->maskPattern . "\n"; + + if ($this->matrix === null) { + $result .= " matrix: null\n"; + } else { + $result .= " matrix:\n"; + $result .= $this->matrix; + } + + $result .= ">>\n"; + + return $result; + } +} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..6f70c20 --- /dev/null +++ b/src/Exception/ExceptionInterface.php @@ -0,0 +1,10 @@ + 100) { + throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100'); + } + + $this->alpha = $alpha; + $this->baseColor = $baseColor; + } + + public function getAlpha() : int + { + return $this->alpha; + } + + public function getBaseColor() : ColorInterface + { + return $this->baseColor; + } + + public function toRgb() : Rgb + { + return $this->baseColor->toRgb(); + } + + public function toCmyk() : Cmyk + { + return $this->baseColor->toCmyk(); + } + + public function toGray() : Gray + { + return $this->baseColor->toGray(); + } +} diff --git a/src/Renderer/Color/Cmyk.php b/src/Renderer/Color/Cmyk.php new file mode 100644 index 0000000..d6de390 --- /dev/null +++ b/src/Renderer/Color/Cmyk.php @@ -0,0 +1,103 @@ + 100) { + throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100'); + } + + if ($magenta < 0 || $magenta > 100) { + throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100'); + } + + if ($yellow < 0 || $yellow > 100) { + throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100'); + } + + if ($black < 0 || $black > 100) { + throw new Exception\InvalidArgumentException('Black must be between 0 and 100'); + } + + $this->cyan = $cyan; + $this->magenta = $magenta; + $this->yellow = $yellow; + $this->black = $black; + } + + public function getCyan() : int + { + return $this->cyan; + } + + public function getMagenta() : int + { + return $this->magenta; + } + + public function getYellow() : int + { + return $this->yellow; + } + + public function getBlack() : int + { + return $this->black; + } + + public function toRgb() : Rgb + { + $k = $this->black / 100; + $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100; + $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100; + $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100; + + return new Rgb( + (int) (-$c * 255 + 255), + (int) (-$m * 255 + 255), + (int) (-$y * 255 + 255) + ); + } + + public function toCmyk() : Cmyk + { + return $this; + } + + public function toGray() : Gray + { + return $this->toRgb()->toGray(); + } +} diff --git a/src/Renderer/Color/ColorInterface.php b/src/Renderer/Color/ColorInterface.php new file mode 100644 index 0000000..b50d1ca --- /dev/null +++ b/src/Renderer/Color/ColorInterface.php @@ -0,0 +1,22 @@ + 100) { + throw new Exception\InvalidArgumentException('Gray must be between 0 and 100'); + } + + $this->gray = (int) $gray; + } + + public function getGray() : int + { + return $this->gray; + } + + public function toRgb() : Rgb + { + return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55)); + } + + public function toCmyk() : Cmyk + { + return new Cmyk(0, 0, 0, 100 - $this->gray); + } + + public function toGray() : Gray + { + return $this; + } +} diff --git a/src/Renderer/Color/Rgb.php b/src/Renderer/Color/Rgb.php new file mode 100644 index 0000000..7935406 --- /dev/null +++ b/src/Renderer/Color/Rgb.php @@ -0,0 +1,88 @@ + 255) { + throw new Exception\InvalidArgumentException('Red must be between 0 and 255'); + } + + if ($green < 0 || $green > 255) { + throw new Exception\InvalidArgumentException('Green must be between 0 and 255'); + } + + if ($blue < 0 || $blue > 255) { + throw new Exception\InvalidArgumentException('Blue must be between 0 and 255'); + } + + $this->red = $red; + $this->green = $green; + $this->blue = $blue; + } + + public function getRed() : int + { + return $this->red; + } + + public function getGreen() : int + { + return $this->green; + } + + public function getBlue() : int + { + return $this->blue; + } + + public function toRgb() : Rgb + { + return $this; + } + + public function toCmyk() : Cmyk + { + $c = 1 - ($this->red / 255); + $m = 1 - ($this->green / 255); + $y = 1 - ($this->blue / 255); + $k = min($c, $m, $y); + + return new Cmyk( + (int) (100 * ($c - $k) / (1 - $k)), + (int) (100 * ($m - $k) / (1 - $k)), + (int) (100 * ($y - $k) / (1 - $k)), + (int) (100 * $k) + ); + } + + public function toGray() : Gray + { + return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55)); + } +} diff --git a/src/Renderer/Eye/CompositeEye.php b/src/Renderer/Eye/CompositeEye.php new file mode 100644 index 0000000..a3e1909 --- /dev/null +++ b/src/Renderer/Eye/CompositeEye.php @@ -0,0 +1,38 @@ +externalEye = $externalEye; + $this->internalEye = $internalEye; + } + + public function getExternalPath() : Path + { + return $this->externalEye->getExternalPath(); + } + + public function getInternalPath() : Path + { + return $this->externalEye->getInternalPath(); + } +} diff --git a/src/Renderer/Eye/EyeInterface.php b/src/Renderer/Eye/EyeInterface.php new file mode 100644 index 0000000..ab68f3c --- /dev/null +++ b/src/Renderer/Eye/EyeInterface.php @@ -0,0 +1,26 @@ +module = $module; + } + + public function getExternalPath() : Path + { + $matrix = new ByteMatrix(7, 7); + + for ($x = 0; $x < 7; ++$x) { + $matrix->set($x, 0, 1); + $matrix->set($x, 6, 1); + } + + for ($y = 1; $y < 6; ++$y) { + $matrix->set(0, $y, 1); + $matrix->set(6, $y, 1); + } + + return $this->module->createPath($matrix)->translate(-3.5, -3.5); + } + + public function getInternalPath() : Path + { + $matrix = new ByteMatrix(3, 3); + + for ($x = 0; $x < 3; ++$x) { + for ($y = 0; $y < 3; ++$y) { + $matrix->set($x, $y, 1); + } + } + + return $this->module->createPath($matrix)->translate(-1.5, -1.5); + } +} diff --git a/src/Renderer/Eye/SimpleCircleEye.php b/src/Renderer/Eye/SimpleCircleEye.php new file mode 100644 index 0000000..64d54ee --- /dev/null +++ b/src/Renderer/Eye/SimpleCircleEye.php @@ -0,0 +1,54 @@ +move(-3.5, -3.5) + ->line(3.5, -3.5) + ->line(3.5, 3.5) + ->line(-3.5, 3.5) + ->close() + ->move(-2.5, -2.5) + ->line(-2.5, 2.5) + ->line(2.5, 2.5) + ->line(2.5, -2.5) + ->close() + ; + } + + public function getInternalPath() : Path + { + return (new Path()) + ->move(1.5, 0) + ->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5) + ->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.) + ->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5) + ->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.) + ->close() + ; + } +} diff --git a/src/Renderer/Eye/SquareEye.php b/src/Renderer/Eye/SquareEye.php new file mode 100644 index 0000000..a3892b4 --- /dev/null +++ b/src/Renderer/Eye/SquareEye.php @@ -0,0 +1,53 @@ +move(-3.5, -3.5) + ->line(3.5, -3.5) + ->line(3.5, 3.5) + ->line(-3.5, 3.5) + ->close() + ->move(-2.5, -2.5) + ->line(-2.5, 2.5) + ->line(2.5, 2.5) + ->line(2.5, -2.5) + ->close() + ; + } + + public function getInternalPath() : Path + { + return (new Path()) + ->move(-1.5, -1.5) + ->line(1.5, -1.5) + ->line(1.5, 1.5) + ->line(-1.5, 1.5) + ->close() + ; + } +} diff --git a/src/Renderer/Image/EpsImageBackEnd.php b/src/Renderer/Image/EpsImageBackEnd.php new file mode 100644 index 0000000..b581b54 --- /dev/null +++ b/src/Renderer/Image/EpsImageBackEnd.php @@ -0,0 +1,376 @@ +eps = "%!PS-Adobe-3.0 EPSF-3.0\n" + . "%%Creator: BaconQrCode\n" + . sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size) + . "%%BeginProlog\n" + . "save\n" + . "50 dict begin\n" + . "/q { gsave } bind def\n" + . "/Q { grestore } bind def\n" + . "/s { scale } bind def\n" + . "/t { translate } bind def\n" + . "/r { rotate } bind def\n" + . "/n { newpath } bind def\n" + . "/m { moveto } bind def\n" + . "/l { lineto } bind def\n" + . "/c { curveto } bind def\n" + . "/z { closepath } bind def\n" + . "/f { eofill } bind def\n" + . "/rgb { setrgbcolor } bind def\n" + . "/cmyk { setcmykcolor } bind def\n" + . "/gray { setgray } bind def\n" + . "%%EndProlog\n" + . "1 -1 s\n" + . sprintf("0 -%d t\n", $size); + + if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) { + return; + } + + $this->eps .= wordwrap( + '0 0 m' + . sprintf(' %s 0 l', (string) $size) + . sprintf(' %s %s l', (string) $size, (string) $size) + . sprintf(' 0 %s l', (string) $size) + . ' z' + . ' ' .$this->getColorSetString($backgroundColor) . " f\n", + 75, + "\n " + ); + } + + public function scale(float $size) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION)); + } + + public function translate(float $x, float $y) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION)); + } + + public function rotate(int $degrees) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= sprintf("%d r\n", $degrees); + } + + public function push() : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= "q\n"; + } + + public function pop() : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= "Q\n"; + } + + public function drawPathWithColor(Path $path, ColorInterface $color) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $fromX = 0; + $fromY = 0; + $this->eps .= wordwrap( + 'n ' + . $this->drawPathOperations($path, $fromX, $fromY) + . ' ' . $this->getColorSetString($color) . " f\n", + 75, + "\n " + ); + } + + public function drawPathWithGradient( + Path $path, + Gradient $gradient, + float $x, + float $y, + float $width, + float $height + ) : void { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $fromX = 0; + $fromY = 0; + $this->eps .= wordwrap( + 'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n", + 75, + "\n " + ); + + $this->createGradientFill($gradient, $x, $y, $width, $height); + } + + public function done() : string + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= "%%TRAILER\nend restore\n%%EOF"; + $blob = $this->eps; + $this->eps = null; + + return $blob; + } + + private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string + { + $pathData = []; + + foreach ($ops as $op) { + switch (true) { + case $op instanceof Move: + $fromX = $toX = round($op->getX(), self::PRECISION); + $fromY = $toY = round($op->getY(), self::PRECISION); + $pathData[] = sprintf('%s %s m', $toX, $toY); + break; + + case $op instanceof Line: + $fromX = $toX = round($op->getX(), self::PRECISION); + $fromY = $toY = round($op->getY(), self::PRECISION); + $pathData[] = sprintf('%s %s l', $toX, $toY); + break; + + case $op instanceof EllipticArc: + $pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY); + break; + + case $op instanceof Curve: + $x1 = round($op->getX1(), self::PRECISION); + $y1 = round($op->getY1(), self::PRECISION); + $x2 = round($op->getX2(), self::PRECISION); + $y2 = round($op->getY2(), self::PRECISION); + $fromX = $x3 = round($op->getX3(), self::PRECISION); + $fromY = $y3 = round($op->getY3(), self::PRECISION); + $pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3); + break; + + case $op instanceof Close: + $pathData[] = 'z'; + break; + + default: + throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); + } + } + + return implode(' ', $pathData); + } + + private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void + { + $startColor = $gradient->getStartColor(); + $endColor = $gradient->getEndColor(); + + if ($startColor instanceof Alpha) { + $startColor = $startColor->getBaseColor(); + } + + $startColorType = get_class($startColor); + + if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) { + $startColorType = Cmyk::class; + $startColor = $startColor->toCmyk(); + } + + if (get_class($endColor) !== $startColorType) { + switch ($startColorType) { + case Cmyk::class: + $endColor = $endColor->toCmyk(); + break; + + case Rgb::class: + $endColor = $endColor->toRgb(); + break; + + case Gray::class: + $endColor = $endColor->toGray(); + break; + } + } + + $this->eps .= "eoclip\n<<\n"; + + if ($gradient->getType() === GradientType::RADIAL()) { + $this->eps .= " /ShadingType 3\n"; + } else { + $this->eps .= " /ShadingType 2\n"; + } + + $this->eps .= " /Extend [ true true ]\n" + . " /AntiAlias true\n"; + + switch ($startColorType) { + case Cmyk::class: + $this->eps .= " /ColorSpace /DeviceCMYK\n"; + break; + + case Rgb::class: + $this->eps .= " /ColorSpace /DeviceRGB\n"; + break; + + case Gray::class: + $this->eps .= " /ColorSpace /DeviceGray\n"; + break; + } + + switch ($gradient->getType()) { + case GradientType::HORIZONTAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y, self::PRECISION), + round($x + $width, self::PRECISION), + round($y, self::PRECISION) + ); + break; + + case GradientType::VERTICAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y, self::PRECISION), + round($x, self::PRECISION), + round($y + $height, self::PRECISION) + ); + break; + + case GradientType::DIAGONAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y, self::PRECISION), + round($x + $width, self::PRECISION), + round($y + $height, self::PRECISION) + ); + break; + + case GradientType::INVERSE_DIAGONAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y + $height, self::PRECISION), + round($x + $width, self::PRECISION), + round($y, self::PRECISION) + ); + break; + + case GradientType::RADIAL(): + $centerX = ($x + $width) / 2; + $centerY = ($y + $height) / 2; + + $this->eps .= sprintf( + " /Coords [ %s %s 0 %s %s %s ]\n", + round($centerX, self::PRECISION), + round($centerY, self::PRECISION), + round($centerX, self::PRECISION), + round($centerY, self::PRECISION), + round(max($width, $height) / 2, self::PRECISION) + ); + break; + } + + $this->eps .= " /Function\n" + . " <<\n" + . " /FunctionType 2\n" + . " /Domain [ 0 1 ]\n" + . sprintf(" /C0 [ %s ]\n", $this->getColorString($startColor)) + . sprintf(" /C1 [ %s ]\n", $this->getColorString($endColor)) + . " /N 1\n" + . " >>\n>>\nshfill\nQ\n"; + } + + private function getColorSetString(ColorInterface $color) : string + { + if ($color instanceof Rgb) { + return $this->getColorString($color) . ' rgb'; + } + + if ($color instanceof Cmyk) { + return $this->getColorString($color) . ' cmyk'; + } + + if ($color instanceof Gray) { + return $this->getColorString($color) . ' gray'; + } + + return $this->getColorSetString($color->toCmyk()); + } + + private function getColorString(ColorInterface $color) : string + { + if ($color instanceof Rgb) { + return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255); + } + + if ($color instanceof Cmyk) { + return sprintf( + '%s %s %s %s', + $color->getCyan() / 100, + $color->getMagenta() / 100, + $color->getYellow() / 100, + $color->getBlack() / 100 + ); + } + + if ($color instanceof Gray) { + return sprintf('%s', $color->getGray() / 100); + } + + return $this->getColorString($color->toCmyk()); + } +} diff --git a/src/Renderer/Image/ImageBackEndInterface.php b/src/Renderer/Image/ImageBackEndInterface.php new file mode 100644 index 0000000..0935819 --- /dev/null +++ b/src/Renderer/Image/ImageBackEndInterface.php @@ -0,0 +1,87 @@ +imageFormat = $imageFormat; + $this->compressionQuality = $compressionQuality; + } + + public function new(int $size, ColorInterface $backgroundColor) : void + { + $this->image = new Imagick(); + $this->image->newImage($size, $size, $this->getColorPixel($backgroundColor)); + $this->image->setImageFormat($this->imageFormat); + $this->image->setCompressionQuality($this->compressionQuality); + $this->draw = new ImagickDraw(); + $this->gradientCount = 0; + $this->matrices = [new TransformationMatrix()]; + $this->matrixIndex = 0; + } + + public function scale(float $size) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->scale($size, $size); + $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] + ->multiply(TransformationMatrix::scale($size)); + } + + public function translate(float $x, float $y) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->translate($x, $y); + $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] + ->multiply(TransformationMatrix::translate($x, $y)); + } + + public function rotate(int $degrees) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->rotate($degrees); + $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] + ->multiply(TransformationMatrix::rotate($degrees)); + } + + public function push() : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->push(); + $this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1]; + } + + public function pop() : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->pop(); + unset($this->matrices[$this->matrixIndex--]); + } + + public function drawPathWithColor(Path $path, ColorInterface $color) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->setFillColor($this->getColorPixel($color)); + $this->drawPath($path); + } + + public function drawPathWithGradient( + Path $path, + Gradient $gradient, + float $x, + float $y, + float $width, + float $height + ) : void { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height)); + $this->drawPath($path); + } + + public function done() : string + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->image->drawImage($this->draw); + $blob = $this->image->getImageBlob(); + $this->draw->clear(); + $this->image->clear(); + $this->draw = null; + $this->image = null; + $this->gradientCount = null; + + return $blob; + } + + private function drawPath(Path $path) : void + { + $this->draw->pathStart(); + + foreach ($path as $op) { + switch (true) { + case $op instanceof Move: + $this->draw->pathMoveToAbsolute($op->getX(), $op->getY()); + break; + + case $op instanceof Line: + $this->draw->pathLineToAbsolute($op->getX(), $op->getY()); + break; + + case $op instanceof EllipticArc: + $this->draw->pathEllipticArcAbsolute( + $op->getXRadius(), + $op->getYRadius(), + $op->getXAxisAngle(), + $op->isLargeArc(), + $op->isSweep(), + $op->getX(), + $op->getY() + ); + break; + + case $op instanceof Curve: + $this->draw->pathCurveToAbsolute( + $op->getX1(), + $op->getY1(), + $op->getX2(), + $op->getY2(), + $op->getX3(), + $op->getY3() + ); + break; + + case $op instanceof Close: + $this->draw->pathClose(); + break; + + default: + throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); + } + } + + $this->draw->pathFinish(); + } + + private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string + { + list($width, $height) = $this->matrices[$this->matrixIndex]->apply($x + $width, $y + $height); + list($x, $y) = $this->matrices[$this->matrixIndex]->apply($x, $y); + $width -= $x; + $height -= $y; + + $startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString(); + $endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString(); + $gradientImage = new Imagick(); + + switch ($gradient->getType()) { + case GradientType::HORIZONTAL(): + $gradientImage->newPseudoImage((int) $height, (int) $width, sprintf( + 'gradient:%s-%s', + $startColor, + $endColor + )); + $gradientImage->rotateImage('transparent', -90); + break; + + case GradientType::VERTICAL(): + $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf( + 'gradient:%s-%s', + $startColor, + $endColor + )); + break; + + case GradientType::DIAGONAL(): + case GradientType::INVERSE_DIAGONAL(): + $gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf( + 'gradient:%s-%s', + $startColor, + $endColor + )); + + if (GradientType::DIAGONAL() === $gradient->getType()) { + $gradientImage->rotateImage('transparent', -45); + } else { + $gradientImage->rotateImage('transparent', -135); + } + + $rotatedWidth = $gradientImage->getImageWidth(); + $rotatedHeight = $gradientImage->getImageHeight(); + + $gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0); + $gradientImage->cropImage( + intdiv($rotatedWidth, 2) - 2, + intdiv($rotatedHeight, 2) - 2, + intdiv($rotatedWidth, 4) + 1, + intdiv($rotatedWidth, 4) + 1 + ); + break; + + case GradientType::RADIAL(): + $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf( + 'radial-gradient:%s-%s', + $startColor, + $endColor + )); + break; + } + + $id = sprintf('g%d', ++$this->gradientCount); + $this->draw->pushPattern($id, 0, 0, $x + $width, $y + $height); + $this->draw->composite(Imagick::COMPOSITE_COPY, $x, $y, $width, $height, $gradientImage); + $this->draw->popPattern(); + return $id; + } + + private function getColorPixel(ColorInterface $color) : ImagickPixel + { + $alpha = 100; + + if ($color instanceof Alpha) { + $alpha = $color->getAlpha(); + $color = $color->getBaseColor(); + } + + if ($color instanceof Rgb) { + return new ImagickPixel(sprintf( + 'rgba(%d, %d, %d, %F)', + $color->getRed(), + $color->getGreen(), + $color->getBlue(), + $alpha / 100 + )); + } + + if ($color instanceof Cmyk) { + return new ImagickPixel(sprintf( + 'cmyka(%d, %d, %d, %d, %F)', + $color->getCyan(), + $color->getMagenta(), + $color->getYellow(), + $color->getBlack(), + $alpha / 100 + )); + } + + if ($color instanceof Gray) { + return new ImagickPixel(sprintf( + 'graya(%d%%, %F)', + $color->getGray(), + $alpha / 100 + )); + } + + return $this->getColorPixel(new Alpha($alpha, $color->toRgb())); + } +} diff --git a/src/Renderer/Image/SvgImageBackEnd.php b/src/Renderer/Image/SvgImageBackEnd.php new file mode 100644 index 0000000..714da6e --- /dev/null +++ b/src/Renderer/Image/SvgImageBackEnd.php @@ -0,0 +1,369 @@ +xmlWriter = new XMLWriter(); + $this->xmlWriter->openMemory(); + + $this->xmlWriter->startDocument('1.0', 'UTF-8'); + $this->xmlWriter->startElement('svg'); + $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg'); + $this->xmlWriter->writeAttribute('version', '1.1'); + $this->xmlWriter->writeAttribute('width', (string) $size); + $this->xmlWriter->writeAttribute('height', (string) $size); + $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size); + + $this->gradientCount = 0; + $this->currentStack = 0; + $this->stack[0] = 0; + + $alpha = 1; + + if ($backgroundColor instanceof Alpha) { + $alpha = $backgroundColor->getAlpha() / 100; + } + + if (0 === $alpha) { + return; + } + + $this->xmlWriter->startElement('rect'); + $this->xmlWriter->writeAttribute('x', '0'); + $this->xmlWriter->writeAttribute('y', '0'); + $this->xmlWriter->writeAttribute('width', (string) $size); + $this->xmlWriter->writeAttribute('height', (string) $size); + $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor)); + + if ($alpha < 1) { + $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); + } + + $this->xmlWriter->endElement(); + } + + public function scale(float $size) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->xmlWriter->writeAttribute( + 'transform', + sprintf('scale(%s)', round($size, self::PRECISION)) + ); + ++$this->stack[$this->currentStack]; + } + + public function translate(float $x, float $y) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->xmlWriter->writeAttribute( + 'transform', + sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION)) + ); + ++$this->stack[$this->currentStack]; + } + + public function rotate(int $degrees) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees)); + ++$this->stack[$this->currentStack]; + } + + public function push() : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->stack[] = 1; + ++$this->currentStack; + } + + public function pop() : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) { + $this->xmlWriter->endElement(); + } + + array_pop($this->stack); + --$this->currentStack; + } + + public function drawPathWithColor(Path $path, ColorInterface $color) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $alpha = 1; + + if ($color instanceof Alpha) { + $alpha = $color->getAlpha() / 100; + } + + $this->startPathElement($path); + $this->xmlWriter->writeAttribute('fill', $this->getColorString($color)); + + if ($alpha < 1) { + $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); + } + + $this->xmlWriter->endElement(); + } + + public function drawPathWithGradient( + Path $path, + Gradient $gradient, + float $x, + float $y, + float $width, + float $height + ) : void { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height); + $this->startPathElement($path); + $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')'); + $this->xmlWriter->endElement(); + } + + public function done() : string + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + foreach ($this->stack as $openElements) { + for ($i = $openElements; $i > 0; --$i) { + $this->xmlWriter->endElement(); + } + } + + $this->xmlWriter->endDocument(); + $blob = $this->xmlWriter->outputMemory(true); + $this->xmlWriter = null; + $this->stack = null; + $this->currentStack = null; + $this->gradientCount = null; + + return $blob; + } + + private function startPathElement(Path $path) : void + { + $pathData = []; + + foreach ($path as $op) { + switch (true) { + case $op instanceof Move: + $pathData[] = sprintf( + 'M%s %s', + round($op->getX(), self::PRECISION), + round($op->getY(), self::PRECISION) + ); + break; + + case $op instanceof Line: + $pathData[] = sprintf( + 'L%s %s', + round($op->getX(), self::PRECISION), + round($op->getY(), self::PRECISION) + ); + break; + + case $op instanceof EllipticArc: + $pathData[] = sprintf( + 'A%s %s %s %u %u %s %s', + round($op->getXRadius(), self::PRECISION), + round($op->getYRadius(), self::PRECISION), + round($op->getXAxisAngle(), self::PRECISION), + $op->isLargeArc(), + $op->isSweep(), + round($op->getX(), self::PRECISION), + round($op->getY(), self::PRECISION) + ); + break; + + case $op instanceof Curve: + $pathData[] = sprintf( + 'C%s %s %s %s %s %s', + round($op->getX1(), self::PRECISION), + round($op->getY1(), self::PRECISION), + round($op->getX2(), self::PRECISION), + round($op->getY2(), self::PRECISION), + round($op->getX3(), self::PRECISION), + round($op->getY3(), self::PRECISION) + ); + break; + + case $op instanceof Close: + $pathData[] = 'Z'; + break; + + default: + throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); + } + } + + $this->xmlWriter->startElement('path'); + $this->xmlWriter->writeAttribute('fill-rule', 'evenodd'); + $this->xmlWriter->writeAttribute('d', implode('', $pathData)); + } + + private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string + { + $this->xmlWriter->startElement('defs'); + + $startColor = $gradient->getStartColor(); + $endColor = $gradient->getEndColor(); + + if ($gradient->getType() === GradientType::RADIAL()) { + $this->xmlWriter->startElement('radialGradient'); + } else { + $this->xmlWriter->startElement('linearGradient'); + } + + $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse'); + + switch ($gradient->getType()) { + case GradientType::HORIZONTAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); + break; + + case GradientType::VERTICAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); + break; + + case GradientType::DIAGONAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); + break; + + case GradientType::INVERSE_DIAGONAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); + break; + + case GradientType::RADIAL(): + $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION)); + $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION)); + $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION)); + break; + } + + $id = sprintf('g%d', ++$this->gradientCount); + $this->xmlWriter->writeAttribute('id', $id); + + $this->xmlWriter->startElement('stop'); + $this->xmlWriter->writeAttribute('offset', '0%'); + $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor)); + + if ($startColor instanceof Alpha) { + $this->xmlWriter->writeAttribute('stop-opacity', $startColor->getAlpha()); + } + + $this->xmlWriter->endElement(); + + $this->xmlWriter->startElement('stop'); + $this->xmlWriter->writeAttribute('offset', '100%'); + $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor)); + + if ($endColor instanceof Alpha) { + $this->xmlWriter->writeAttribute('stop-opacity', $endColor->getAlpha()); + } + + $this->xmlWriter->endElement(); + + $this->xmlWriter->endElement(); + $this->xmlWriter->endElement(); + + return $id; + } + + private function getColorString(ColorInterface $color) : string + { + $color = $color->toRgb(); + + return sprintf( + '#%02x%02x%02x', + $color->getRed(), + $color->getGreen(), + $color->getBlue() + ); + } +} diff --git a/src/Renderer/Image/TransformationMatrix.php b/src/Renderer/Image/TransformationMatrix.php new file mode 100644 index 0000000..b41ee09 --- /dev/null +++ b/src/Renderer/Image/TransformationMatrix.php @@ -0,0 +1,67 @@ +values = [1, 0, 0, 1, 0, 0]; + } + + public function multiply(self $other) : self + { + $matrix = new self(); + $matrix->values[0] = $this->values[0] * $other->values[0] + $this->values[2] * $other->values[1]; + $matrix->values[1] = $this->values[1] * $other->values[0] + $this->values[3] * $other->values[1]; + $matrix->values[2] = $this->values[0] * $other->values[2] + $this->values[2] * $other->values[3]; + $matrix->values[3] = $this->values[1] * $other->values[2] + $this->values[3] * $other->values[3]; + $matrix->values[4] = $this->values[0] * $other->values[4] + $this->values[2] * $other->values[5] + + $this->values[4]; + $matrix->values[5] = $this->values[1] * $other->values[4] + $this->values[3] * $other->values[5] + + $this->values[5]; + + return $matrix; + } + + public static function scale(float $size) : self + { + $matrix = new self(); + $matrix->values = [$size, 0, 0, $size, 0, 0]; + return $matrix; + } + + public static function translate(float $x, float $y) : self + { + $matrix = new self(); + $matrix->values = [1, 0, 0, 1, $x, $y]; + return $matrix; + } + + public static function rotate(int $degrees) : self + { + $matrix = new self(); + $matrix->values = [cos($degrees), sin($degrees), -sin($degrees), cos($degrees), 0, 0]; + return $matrix; + } + + + /** + * Applies this matrix onto a point and returns the resulting viewport point. + * + * @return float[] + */ + public function apply(float $x, float $y) : array + { + return [ + $x * $this->values[0] + $y * $this->values[2] + $this->values[4], + $x * $this->values[2] + $x * $this->values[3] + $this->values[5], + ]; + } +} diff --git a/src/Renderer/ImageRenderer.php b/src/Renderer/ImageRenderer.php new file mode 100644 index 0000000..ab16276 --- /dev/null +++ b/src/Renderer/ImageRenderer.php @@ -0,0 +1,152 @@ +rendererStyle = $rendererStyle; + $this->imageBackEnd = $imageBackEnd; + } + + /** + * @throws InvalidArgumentException if matrix width doesn't match height + */ + public function render(QrCode $qrCode) : string + { + $size = $this->rendererStyle->getSize(); + $margin = $this->rendererStyle->getMargin(); + $matrix = $qrCode->getMatrix(); + $matrixSize = $matrix->getWidth(); + + if ($matrixSize !== $matrix->getHeight()) { + throw new InvalidArgumentException('Matrix must have the same width and height'); + } + + $totalSize = $matrixSize + ($margin * 2); + $moduleSize = $size / $totalSize; + $fill = $this->rendererStyle->getFill(); + + $this->imageBackEnd->new($size, $fill->getBackgroundColor()); + $this->imageBackEnd->scale((float) $moduleSize); + $this->imageBackEnd->translate((float) $margin, (float) $margin); + + $module = $this->rendererStyle->getModule(); + $moduleMatrix = clone $matrix; + MatrixUtil::removePositionDetectionPatterns($moduleMatrix); + $modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix)); + + if ($fill->hasGradientFill()) { + $this->imageBackEnd->drawPathWithGradient( + $modulePath, + $fill->getForegroundGradient(), + 0, + 0, + $matrixSize, + $matrixSize + ); + } else { + $this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor()); + } + + return $this->imageBackEnd->done(); + } + + private function drawEyes(int $matrixSize, Path $modulePath) : Path + { + $fill = $this->rendererStyle->getFill(); + + $eye = $this->rendererStyle->getEye(); + $externalPath = $eye->getExternalPath(); + $internalPath = $eye->getInternalPath(); + + $modulePath = $this->drawEye( + $externalPath, + $internalPath, + $fill->getTopLeftEyeFill(), + 3.5, + 3.5, + 0, + $modulePath + ); + $modulePath = $this->drawEye( + $externalPath, + $internalPath, + $fill->getTopRightEyeFill(), + $matrixSize - 3.5, + 3.5, + 90, + $modulePath + ); + $modulePath = $this->drawEye( + $externalPath, + $internalPath, + $fill->getBottomLeftEyeFill(), + 3.5, + $matrixSize - 3.5, + -90, + $modulePath + ); + + return $modulePath; + } + + private function drawEye( + Path $externalPath, + Path $internalPath, + EyeFill $fill, + float $xTranslation, + float $yTranslation, + int $rotation, + Path $modulePath + ) : Path { + if ($fill->inheritsBothColors()) { + return $modulePath + ->append($externalPath->translate($xTranslation, $yTranslation)) + ->append($internalPath->translate($xTranslation, $yTranslation)); + } + + $this->imageBackEnd->push(); + $this->imageBackEnd->translate($xTranslation, $yTranslation); + + if (0 !== $rotation) { + $this->imageBackEnd->rotate($rotation); + } + + if ($fill->inheritsExternalColor()) { + $modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation)); + } else { + $this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor()); + } + + if ($fill->inheritsInternalColor()) { + $modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation)); + } else { + $this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor()); + } + + $this->imageBackEnd->pop(); + + return $modulePath; + } +} diff --git a/src/Renderer/Module/DotsModule.php b/src/Renderer/Module/DotsModule.php new file mode 100644 index 0000000..f536e5a --- /dev/null +++ b/src/Renderer/Module/DotsModule.php @@ -0,0 +1,63 @@ + 1) { + throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)'); + } + + $this->size = $size; + } + + public function createPath(ByteMatrix $matrix) : Path + { + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); + $path = new Path(); + $halfSize = $this->size / 2; + $margin = (1 - $this->size) / 2; + + for ($y = 0; $y < $height; ++$y) { + for ($x = 0; $x < $width; ++$x) { + if (! $matrix->get($x, $y)) { + continue; + } + + $pathX = $x + $margin; + $pathY = $y + $margin; + + $path = $path + ->move($pathX + $this->size, $pathY + $halfSize) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY + $this->size) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX, $pathY + $halfSize) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $this->size, $pathY + $halfSize) + ->close() + ; + } + } + + return $path; + } +} diff --git a/src/Renderer/Module/EdgeIterator/Edge.php b/src/Renderer/Module/EdgeIterator/Edge.php new file mode 100644 index 0000000..90482f2 --- /dev/null +++ b/src/Renderer/Module/EdgeIterator/Edge.php @@ -0,0 +1,100 @@ + + */ + private $points = []; + + /** + * @var array|null + */ + private $simplifiedPoints; + + /** + * @var int + */ + private $minX = PHP_INT_MAX; + + /** + * @var int + */ + private $minY = PHP_INT_MAX; + + /** + * @var int + */ + private $maxX = -1; + + /** + * @var int + */ + private $maxY = -1; + + public function __construct(bool $positive) + { + $this->positive = $positive; + } + + public function addPoint(int $x, int $y) : void + { + $this->points[] = [$x, $y]; + $this->minX = min($this->minX, $x); + $this->minY = min($this->minY, $y); + $this->maxX = max($this->maxX, $x); + $this->maxY = max($this->maxY, $y); + } + + public function isPositive() : bool + { + return $this->positive; + } + + /** + * @return array + */ + public function getPoints() : array + { + return $this->points; + } + + public function getMaxX() : int + { + return $this->maxX; + } + + public function getSimplifiedPoints() : array + { + if (null !== $this->simplifiedPoints) { + return $this->simplifiedPoints; + } + + $points = []; + $length = count($this->points); + + for ($i = 0; $i < $length; ++$i) { + $previousPoint = $this->points[(0 === $i ? $length : $i) - 1]; + $nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1]; + $currentPoint = $this->points[$i]; + + if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0]) + || ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1]) + ) { + continue; + } + + $points[] = $currentPoint; + } + + return $this->simplifiedPoints = $points; + } +} diff --git a/src/Renderer/Module/EdgeIterator/EdgeIterator.php b/src/Renderer/Module/EdgeIterator/EdgeIterator.php new file mode 100644 index 0000000..af52d52 --- /dev/null +++ b/src/Renderer/Module/EdgeIterator/EdgeIterator.php @@ -0,0 +1,169 @@ +bytes = iterator_to_array($matrix->getBytes()); + $this->size = count($this->bytes); + $this->width = $matrix->getWidth(); + $this->height = $matrix->getHeight(); + } + + /** + * @return Edge[] + */ + public function getIterator() : Traversable + { + $originalBytes = $this->bytes; + $point = $this->findNext(0, 0); + + while (null !== $point) { + $edge = $this->findEdge($point[0], $point[1]); + $this->xorEdge($edge); + + yield $edge; + + $point = $this->findNext($point[0], $point[1]); + } + + $this->bytes = $originalBytes; + } + + /** + * @return int[]|null + */ + private function findNext(int $x, int $y) : ?array + { + $i = $this->width * $y + $x; + + while ($i < $this->size && 1 !== $this->bytes[$i]) { + ++$i; + } + + if ($i < $this->size) { + return $this->pointOf($i); + } + + return null; + } + + private function findEdge(int $x, int $y) : Edge + { + $edge = new Edge($this->isSet($x, $y)); + $startX = $x; + $startY = $y; + $dirX = 0; + $dirY = 1; + + while (true) { + $edge->addPoint($x, $y); + $x += $dirX; + $y += $dirY; + + if ($x === $startX && $y === $startY) { + break; + } + + $left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2); + $right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2); + + if ($right && ! $left) { + $tmp = $dirX; + $dirX = -$dirY; + $dirY = $tmp; + } elseif ($right) { + $tmp = $dirX; + $dirX = -$dirY; + $dirY = $tmp; + } elseif (! $left) { + $tmp = $dirX; + $dirX = $dirY; + $dirY = -$tmp; + } + } + + return $edge; + } + + private function xorEdge(Edge $path) : void + { + $points = $path->getPoints(); + $y1 = $points[0][1]; + $length = count($points); + $maxX = $path->getMaxX(); + + for ($i = 1; $i < $length; ++$i) { + $y = $points[$i][1]; + + if ($y === $y1) { + continue; + } + + $x = $points[$i][0]; + $minY = min($y1, $y); + + for ($j = $x; $j < $maxX; ++$j) { + $this->flip($j, $minY); + } + + $y1 = $y; + } + } + + private function isSet(int $x, int $y) : bool + { + return ( + $x >= 0 + && $x < $this->width + && $y >= 0 + && $y < $this->height + ) && 1 === $this->bytes[$this->width * $y + $x]; + } + + /** + * @return int[] + */ + private function pointOf(int $i) : array + { + $y = intdiv($i, $this->width); + return [$i - $y * $this->width, $y]; + } + + private function flip(int $x, int $y) : void + { + $this->bytes[$this->width * $y + $x] = ( + $this->isSet($x, $y) ? 0 : 1 + ); + } +} diff --git a/src/Renderer/Module/ModuleInterface.php b/src/Renderer/Module/ModuleInterface.php new file mode 100644 index 0000000..0ccb0e0 --- /dev/null +++ b/src/Renderer/Module/ModuleInterface.php @@ -0,0 +1,18 @@ + 1) { + throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)'); + } + + $this->intensity = $intensity / 2; + } + + public function createPath(ByteMatrix $matrix) : Path + { + $path = new Path(); + + foreach (new EdgeIterator($matrix) as $edge) { + $points = $edge->getSimplifiedPoints(); + $length = count($points); + + $currentPoint = $points[0]; + $nextPoint = $points[1]; + $horizontal = ($currentPoint[1] === $nextPoint[1]); + + if ($horizontal) { + $right = $nextPoint[0] > $currentPoint[0]; + $path = $path->move( + $currentPoint[0] + ($right ? $this->intensity : -$this->intensity), + $currentPoint[1] + ); + } else { + $up = $nextPoint[0] < $currentPoint[0]; + $path = $path->move( + $currentPoint[0], + $currentPoint[1] + ($up ? -$this->intensity : $this->intensity) + ); + } + + for ($i = 1; $i <= $length; ++$i) { + if ($i === $length) { + $previousPoint = $points[$length - 1]; + $currentPoint = $points[0]; + $nextPoint = $points[1]; + } else { + $previousPoint = $points[(0 === $i ? $length : $i) - 1]; + $currentPoint = $points[$i]; + $nextPoint = $points[($length - 1 === $i ? -1 : $i) + 1]; + } + + $horizontal = ($previousPoint[1] === $currentPoint[1]); + + if ($horizontal) { + $right = $previousPoint[0] < $currentPoint[0]; + $up = $nextPoint[1] < $currentPoint[1]; + $sweep = ($up xor $right); + + if ($this->intensity < 0.5 + || ($right && $previousPoint[0] !== $currentPoint[0] - 1) + || (! $right && $previousPoint[0] - 1 !== $currentPoint[0]) + ) { + $path = $path->line( + $currentPoint[0] + ($right ? -$this->intensity : $this->intensity), + $currentPoint[1] + ); + } + + $path = $path->ellipticArc( + $this->intensity, + $this->intensity, + 0, + false, + $sweep, + $currentPoint[0], + $currentPoint[1] + ($up ? -$this->intensity : $this->intensity) + ); + } else { + $up = $previousPoint[1] > $currentPoint[1]; + $right = $nextPoint[0] > $currentPoint[0]; + $sweep = ! ($up xor $right); + + if ($this->intensity < 0.5 + || ($up && $previousPoint[1] !== $currentPoint[1] + 1) + || (! $up && $previousPoint[0] + 1 !== $currentPoint[0]) + ) { + $path = $path->line( + $currentPoint[0], + $currentPoint[1] + ($up ? $this->intensity : -$this->intensity) + ); + } + + $path = $path->ellipticArc( + $this->intensity, + $this->intensity, + 0, + false, + $sweep, + $currentPoint[0] + ($right ? $this->intensity : -$this->intensity), + $currentPoint[1] + ); + } + } + + $path = $path->close(); + } + + return $path; + } +} diff --git a/src/Renderer/Module/SquareModule.php b/src/Renderer/Module/SquareModule.php new file mode 100644 index 0000000..9ab4607 --- /dev/null +++ b/src/Renderer/Module/SquareModule.php @@ -0,0 +1,47 @@ +getSimplifiedPoints(); + $length = count($points); + $path = $path->move($points[0][0], $points[0][1]); + + for ($i = 1; $i < $length; ++$i) { + $path = $path->line($points[$i][0], $points[$i][1]); + } + + $path = $path->close(); + } + + return $path; + } +} diff --git a/src/Renderer/Path/Close.php b/src/Renderer/Path/Close.php new file mode 100644 index 0000000..b07feb0 --- /dev/null +++ b/src/Renderer/Path/Close.php @@ -0,0 +1,29 @@ +x1 = $x1; + $this->y1 = $y1; + $this->x2 = $x2; + $this->y2 = $y2; + $this->x3 = $x3; + $this->y3 = $y3; + } + + public function getX1() : float + { + return $this->x1; + } + + public function getY1() : float + { + return $this->y1; + } + + public function getX2() : float + { + return $this->x2; + } + + public function getY2() : float + { + return $this->y2; + } + + public function getX3() : float + { + return $this->x3; + } + + public function getY3() : float + { + return $this->y3; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self( + $this->x1 + $x, + $this->y1 + $y, + $this->x2 + $x, + $this->y2 + $y, + $this->x3 + $x, + $this->y3 + $y + ); + } +} diff --git a/src/Renderer/Path/EllipticArc.php b/src/Renderer/Path/EllipticArc.php new file mode 100644 index 0000000..eff7deb --- /dev/null +++ b/src/Renderer/Path/EllipticArc.php @@ -0,0 +1,278 @@ +xRadius = abs($xRadius); + $this->yRadius = abs($yRadius); + $this->xAxisAngle = $xAxisAngle % 360; + $this->largeArc = $largeArc; + $this->sweep = $sweep; + $this->x = $x; + $this->y = $y; + } + + public function getXRadius() : float + { + return $this->xRadius; + } + + public function getYRadius() : float + { + return $this->yRadius; + } + + public function getXAxisAngle() : float + { + return $this->xAxisAngle; + } + + public function isLargeArc() : bool + { + return $this->largeArc; + } + + public function isSweep() : bool + { + return $this->sweep; + } + + public function getX() : float + { + return $this->x; + } + + public function getY() : float + { + return $this->y; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self( + $this->xRadius, + $this->yRadius, + $this->xAxisAngle, + $this->largeArc, + $this->sweep, + $this->x + $x, + $this->y + $y + ); + } + + /** + * Converts the elliptic arc to multiple curves. + * + * Since not all image back ends support elliptic arcs, this method allows to convert the arc into multiple curves + * resembling the same result. + * + * @see https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ + * @return array + */ + public function toCurves(float $fromX, float $fromY) : array + { + if (sqrt(($fromX - $this->x) ** 2 + ($fromY - $this->y) ** 2) < self::ZERO_TOLERANCE) { + return []; + } + + if ($this->xRadius < self::ZERO_TOLERANCE || $this->yRadius < self::ZERO_TOLERANCE) { + return [new Line($this->x, $this->y)]; + } + + return $this->createCurves($fromX, $fromY); + } + + /** + * @return Curve[] + */ + private function createCurves(float $fromX, $fromY) : array + { + $xAngle = deg2rad($this->xAxisAngle); + list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) = + $this->calculateCenterPointParameters($fromX, $fromY, $xAngle); + + $s = $startAngle; + $e = $s + $deltaAngle; + $sign = ($e < $s) ? -1 : 1; + $remain = abs($e - $s); + $p1 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s); + $curves = []; + + while ($remain > self::ZERO_TOLERANCE) { + $step = min($remain, pi() / 2); + $signStep = $step * $sign; + $p2 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s + $signStep); + + $alphaT = tan($signStep / 2); + $alpha = sin($signStep) * (sqrt(4 + 3 * $alphaT ** 2) - 1) / 3; + $d1 = self::derivative($radiusX, $radiusY, $xAngle, $s); + $d2 = self::derivative($radiusX, $radiusY, $xAngle, $s + $signStep); + + $curves[] = new Curve( + $p1[0] + $alpha * $d1[0], + $p1[1] + $alpha * $d1[1], + $p2[0] - $alpha * $d2[0], + $p2[1] - $alpha * $d2[1], + $p2[0], + $p2[1] + ); + + $s += $signStep; + $remain -= $step; + $p1 = $p2; + } + + return $curves; + } + + /** + * @return float[] + */ + private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle) + { + $rX = $this->xRadius; + $rY = $this->yRadius; + + // F.6.5.1 + $dx2 = ($fromX - $this->x) / 2; + $dy2 = ($fromY - $this->y) / 2; + $x1p = cos($xAngle) * $dx2 + sin($xAngle) * $dy2; + $y1p = -sin($xAngle) * $dx2 + cos($xAngle) * $dy2; + + // F.6.5.2 + $rxs = $rX ** 2; + $rys = $rY ** 2; + $x1ps = $x1p ** 2; + $y1ps = $y1p ** 2; + $cr = $x1ps / $rxs + $y1ps / $rys; + + if ($cr > 1) { + $s = sqrt($cr); + $rX *= $s; + $rY *= $s; + $rxs = $rX ** 2; + $rys = $rY ** 2; + } + + $dq = ($rxs * $y1ps + $rys * $x1ps); + $pq = ($rxs * $rys - $dq) / $dq; + $q = sqrt(max(0, $pq)); + + if ($this->largeArc === $this->sweep) { + $q = -$q; + } + + $cxp = $q * $rX * $y1p / $rY; + $cyp = -$q * $rY * $x1p / $rX; + + // F.6.5.3 + $cx = cos($xAngle) * $cxp - sin($xAngle) * $cyp + ($fromX + $this->x) / 2; + $cy = sin($xAngle) * $cxp + cos($xAngle) * $cyp + ($fromY + $this->y) / 2; + + // F.6.5.5 + $theta = self::angle(1, 0, ($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY); + + // F.6.5.6 + $delta = self::angle(($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY, (-$x1p - $cxp) / $rX, (-$y1p - $cyp) / $rY); + $delta = fmod($delta, pi() * 2); + + if (! $this->sweep) { + $delta -= 2 * pi(); + } + + return [$cx, $cy, $rX, $rY, $theta, $delta]; + } + + private static function angle(float $ux, float $uy, float $vx, float $vy) : float + { + // F.6.5.4 + $dot = $ux * $vx + $uy * $vy; + $length = sqrt($ux ** 2 + $uy ** 2) * sqrt($vx ** 2 + $vy ** 2); + $angle = acos(min(1, max(-1, $dot / $length))); + + if (($ux * $vy - $uy * $vx) < 0) { + return -$angle; + } + + return $angle; + } + + /** + * @return float[] + */ + private static function point( + float $centerX, + float $centerY, + float $radiusX, + float $radiusY, + float $xAngle, + float $angle + ) : array { + return [ + $centerX + $radiusX * cos($xAngle) * cos($angle) - $radiusY * sin($xAngle) * sin($angle), + $centerY + $radiusX * sin($xAngle) * cos($angle) + $radiusY * cos($xAngle) * sin($angle), + ]; + } + + /** + * @return float[] + */ + private static function derivative(float $radiusX, float $radiusY, float $xAngle, float $angle) : array + { + return [ + -$radiusX * cos($xAngle) * sin($angle) - $radiusY * sin($xAngle) * cos($angle), + -$radiusX * sin($xAngle) * sin($angle) + $radiusY * cos($xAngle) * cos($angle), + ]; + } +} diff --git a/src/Renderer/Path/Line.php b/src/Renderer/Path/Line.php new file mode 100644 index 0000000..3149a39 --- /dev/null +++ b/src/Renderer/Path/Line.php @@ -0,0 +1,41 @@ +x = $x; + $this->y = $y; + } + + public function getX() : float + { + return $this->x; + } + + public function getY() : float + { + return $this->y; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self($this->x + $x, $this->y + $y); + } +} diff --git a/src/Renderer/Path/Move.php b/src/Renderer/Path/Move.php new file mode 100644 index 0000000..007b77c --- /dev/null +++ b/src/Renderer/Path/Move.php @@ -0,0 +1,41 @@ +x = $x; + $this->y = $y; + } + + public function getX() : float + { + return $this->x; + } + + public function getY() : float + { + return $this->y; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self($this->x + $x, $this->x + $y); + } +} diff --git a/src/Renderer/Path/OperationInterface.php b/src/Renderer/Path/OperationInterface.php new file mode 100644 index 0000000..a5fa0ed --- /dev/null +++ b/src/Renderer/Path/OperationInterface.php @@ -0,0 +1,12 @@ +operations[] = new Move($x, $y); + return $path; + } + + /** + * Draws a line from the current position to another position. + */ + public function line(float $x, float $y) : self + { + $path = clone $this; + $path->operations[] = new Line($x, $y); + return $path; + } + + /** + * Draws an elliptic arc from the current position to another position. + */ + public function ellipticArc( + float $xRadius, + float $yRadius, + float $xAxisRotation, + bool $largeArc, + bool $sweep, + float $x, + float $y + ) : self { + $path = clone $this; + $path->operations[] = new EllipticArc($xRadius, $yRadius, $xAxisRotation, $largeArc, $sweep, $x, $y); + return $path; + } + + /** + * Draws a curve from the current position to another position. + */ + public function curve(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3) : self + { + $path = clone $this; + $path->operations[] = new Curve($x1, $y1, $x2, $y2, $x3, $y3); + return $path; + } + + /** + * Closes a sub-path. + */ + public function close() : self + { + $path = clone $this; + $path->operations[] = Close::instance(); + return $path; + } + + /** + * Appends another path to this one. + */ + public function append(self $other) : self + { + $path = clone $this; + $path->operations = array_merge($this->operations, $other->operations); + return $path; + } + + public function translate(float $x, float $y) : self + { + $path = new self(); + + foreach ($this->operations as $operation) { + $path->operations[] = $operation->translate($x, $y); + } + + return $path; + } + + /** + * @return OperationInterface[]|Traversable + */ + public function getIterator() : Traversable + { + foreach ($this->operations as $operation) { + yield $operation; + } + } +} diff --git a/src/Renderer/PlainTextRenderer.php b/src/Renderer/PlainTextRenderer.php new file mode 100644 index 0000000..8aa7652 --- /dev/null +++ b/src/Renderer/PlainTextRenderer.php @@ -0,0 +1,86 @@ +margin = $margin; + } + + /** + * @throws InvalidArgumentException if matrix width doesn't match height + */ + public function render(QrCode $qrCode) : string + { + $matrix = $qrCode->getMatrix(); + $matrixSize = $matrix->getWidth(); + + if ($matrixSize !== $matrix->getHeight()) { + throw new InvalidArgumentException('Matrix must have the same width and height'); + } + + $rows = $matrix->getArray()->toArray(); + + if (0 !== $matrixSize % 2) { + $rows[] = array_fill(0, $matrixSize, 0); + } + + $horizontalMargin = str_repeat(self::EMPTY_BLOCK, $this->margin); + $result = str_repeat("\n", (int) ceil($this->margin / 2)); + + for ($i = 0; $i < $matrixSize; $i += 2) { + $result .= $horizontalMargin; + + $upperRow = $rows[$i]; + $lowerRow = $rows[$i + 1]; + + for ($j = 0; $j < $matrixSize; ++$j) { + $upperBit = $upperRow[$j]; + $lowerBit = $lowerRow[$j]; + + if ($upperBit) { + $result .= $lowerBit ? self::FULL_BLOCK : self::UPPER_HALF_BLOCK; + } else { + $result .= $lowerBit ? self::LOWER_HALF_BLOCK : self::EMPTY_BLOCK; + } + } + + $result .= $horizontalMargin . "\n"; + } + + $result .= str_repeat("\n", (int) ceil($this->margin / 2)); + + return $result; + } +} diff --git a/src/Renderer/RendererInterface.php b/src/Renderer/RendererInterface.php new file mode 100644 index 0000000..b0aae39 --- /dev/null +++ b/src/Renderer/RendererInterface.php @@ -0,0 +1,11 @@ +externalColor = $externalColor; + $this->internalColor = $internalColor; + } + + public static function uniform(ColorInterface $color) : self + { + return new self($color, $color); + } + + public static function inherit() : self + { + return self::$inherit ?: self::$inherit = new self(null, null); + } + + public function inheritsBothColors() : bool + { + return null === $this->externalColor && null === $this->internalColor; + } + + public function inheritsExternalColor() : bool + { + return null === $this->externalColor; + } + + public function inheritsInternalColor() : bool + { + return null === $this->internalColor; + } + + public function getExternalColor() : ColorInterface + { + if (null === $this->externalColor) { + throw new RuntimeException('External eye color inherits foreground color'); + } + + return $this->externalColor; + } + + public function getInternalColor() : ColorInterface + { + if (null === $this->internalColor) { + throw new RuntimeException('Internal eye color inherits foreground color'); + } + + return $this->internalColor; + } +} diff --git a/src/Renderer/RendererStyle/Fill.php b/src/Renderer/RendererStyle/Fill.php new file mode 100644 index 0000000..d54268e --- /dev/null +++ b/src/Renderer/RendererStyle/Fill.php @@ -0,0 +1,168 @@ +backgroundColor = $backgroundColor; + $this->foregroundColor = $foregroundColor; + $this->foregroundGradient = $foregroundGradient; + $this->topLeftEyeFill = $topLeftEyeFill; + $this->topRightEyeFill = $topRightEyeFill; + $this->bottomLeftEyeFill = $bottomLeftEyeFill; + } + + public static function default() : self + { + return self::$default ?: self::$default = self::uniformColor(new Gray(100), new Gray(0)); + } + + public static function withForegroundColor( + ColorInterface $backgroundColor, + ColorInterface $foregroundColor, + EyeFill $topLeftEyeFill, + EyeFill $topRightEyeFill, + EyeFill $bottomLeftEyeFill + ) : self { + return new self( + $backgroundColor, + $foregroundColor, + null, + $topLeftEyeFill, + $topRightEyeFill, + $bottomLeftEyeFill + ); + } + + public static function withForegroundGradient( + ColorInterface $backgroundColor, + Gradient $foregroundGradient, + EyeFill $topLeftEyeFill, + EyeFill $topRightEyeFill, + EyeFill $bottomLeftEyeFill + ) : self { + return new self( + $backgroundColor, + null, + $foregroundGradient, + $topLeftEyeFill, + $topRightEyeFill, + $bottomLeftEyeFill + ); + } + + public static function uniformColor(ColorInterface $backgroundColor, ColorInterface $foregroundColor) : self + { + return new self( + $backgroundColor, + $foregroundColor, + null, + EyeFill::inherit(), + EyeFill::inherit(), + EyeFill::inherit() + ); + } + + public static function uniformGradient(ColorInterface $backgroundColor, Gradient $foregroundGradient) : self + { + return new self( + $backgroundColor, + null, + $foregroundGradient, + EyeFill::inherit(), + EyeFill::inherit(), + EyeFill::inherit() + ); + } + + public function hasGradientFill() : bool + { + return null !== $this->foregroundGradient; + } + + public function getBackgroundColor() : ColorInterface + { + return $this->backgroundColor; + } + + public function getForegroundColor() : ColorInterface + { + if (null === $this->foregroundColor) { + throw new RuntimeException('Fill uses a gradient, thus no foreground color is available'); + } + + return $this->foregroundColor; + } + + public function getForegroundGradient() : Gradient + { + if (null === $this->foregroundGradient) { + throw new RuntimeException('Fill uses a single color, thus no foreground gradient is available'); + } + + return $this->foregroundGradient; + } + + public function getTopLeftEyeFill() : EyeFill + { + return $this->topLeftEyeFill; + } + + public function getTopRightEyeFill() : EyeFill + { + return $this->topRightEyeFill; + } + + public function getBottomLeftEyeFill() : EyeFill + { + return $this->bottomLeftEyeFill; + } +} diff --git a/src/Renderer/RendererStyle/Gradient.php b/src/Renderer/RendererStyle/Gradient.php new file mode 100644 index 0000000..3813dfd --- /dev/null +++ b/src/Renderer/RendererStyle/Gradient.php @@ -0,0 +1,46 @@ +startColor = $startColor; + $this->endColor = $endColor; + $this->type = $type; + } + + public function getStartColor() : ColorInterface + { + return $this->startColor; + } + + public function getEndColor() : ColorInterface + { + return $this->endColor; + } + + public function getType() : GradientType + { + return $this->type; + } +} diff --git a/src/Renderer/RendererStyle/GradientType.php b/src/Renderer/RendererStyle/GradientType.php new file mode 100644 index 0000000..c1ca754 --- /dev/null +++ b/src/Renderer/RendererStyle/GradientType.php @@ -0,0 +1,22 @@ +margin = $margin; + $this->size = $size; + $this->module = $module ?: SquareModule::instance(); + $this->eye = $eye ?: new ModuleEye($this->module); + $this->fill = $fill ?: Fill::default(); + } + + public function withSize(int $size) : self + { + $style = clone $this; + $style->size = $size; + return $style; + } + + public function withMargin(int $margin) : self + { + $style = clone $this; + $style->margin = $margin; + return $style; + } + + public function getSize() : int + { + return $this->size; + } + + public function getMargin() : int + { + return $this->margin; + } + + public function getModule() : ModuleInterface + { + return $this->module; + } + + public function getEye() : EyeInterface + { + return $this->eye; + } + + public function getFill() : Fill + { + return $this->fill; + } +} diff --git a/src/Writer.php b/src/Writer.php new file mode 100644 index 0000000..6688901 --- /dev/null +++ b/src/Writer.php @@ -0,0 +1,68 @@ +renderer = $renderer; + } + + /** + * Writes QR code and returns it as string. + * + * Content is a string which *should* be encoded in UTF-8, in case there are + * non ASCII-characters present. + * + * @throws InvalidArgumentException if the content is empty + */ + public function writeString( + string $content, + string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, + ?ErrorCorrectionLevel $ecLevel = null + ) : string { + if (strlen($content) === 0) { + throw new InvalidArgumentException('Found empty contents'); + } + + if (null === $ecLevel) { + $ecLevel = ErrorCorrectionLevel::L(); + } + + return $this->renderer->render(Encoder::encode($content, $ecLevel, $encoding)); + } + + /** + * Writes QR code to a file. + * + * @see Writer::writeString() + */ + public function writeFile( + string $content, + string $filename, + string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, + ?ErrorCorrectionLevel $ecLevel = null + ) : void { + file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel)); + } +} diff --git a/tests/BaconQrCode/Common/BitArrayTest.php b/test/Common/BitArrayTest.php similarity index 62% rename from tests/BaconQrCode/Common/BitArrayTest.php rename to test/Common/BitArrayTest.php index 81bcbce..7943709 100644 --- a/tests/BaconQrCode/Common/BitArrayTest.php +++ b/test/Common/BitArrayTest.php @@ -1,66 +1,61 @@ assertFalse($array->get($i)); $array->set($i); $this->assertTrue($array->get($i)); } } - public function testGetNextSet1() + public function testGetNextSet1() : void { $array = new BitArray(32); - for ($i = 0; $i < $array->getSize(); $i++) { + for ($i = 0; $i < $array->getSize(); ++$i) { $this->assertEquals($i, 32, '', $array->getNextSet($i)); } $array = new BitArray(33); - for ($i = 0; $i < $array->getSize(); $i++) { + for ($i = 0; $i < $array->getSize(); ++$i) { $this->assertEquals($i, 33, '', $array->getNextSet($i)); } } - public function testGetNextSet2() + public function testGetNextSet2() : void { $array = new BitArray(33); - for ($i = 0; $i < $array->getSize(); $i++) { + for ($i = 0; $i < $array->getSize(); ++$i) { $this->assertEquals($i, $i <= 31 ? 31 : 33, '', $array->getNextSet($i)); } $array = new BitArray(33); - for ($i = 0; $i < $array->getSize(); $i++) { + for ($i = 0; $i < $array->getSize(); ++$i) { $this->assertEquals($i, 32, '', $array->getNextSet($i)); } } - public function testGetNextSet3() + public function testGetNextSet3() : void { $array = new BitArray(63); $array->set(31); $array->set(32); - for ($i = 0; $i < $array->getSize(); $i++) { + for ($i = 0; $i < $array->getSize(); ++$i) { if ($i <= 31) { $expected = 31; } elseif ($i <= 32) { @@ -73,13 +68,13 @@ public function testGetNextSet3() } } - public function testGetNextSet4() + public function testGetNextSet4() : void { $array = new BitArray(63); $array->set(33); $array->set(40); - for ($i = 0; $i < $array->getSize(); $i++) { + for ($i = 0; $i < $array->getSize(); ++$i) { if ($i <= 33) { $expected = 33; } elseif ($i <= 40) { @@ -92,30 +87,26 @@ public function testGetNextSet4() } } - public function testGetNextSet5() + public function testGetNextSet5() : void { - if (defined('MT_RAND_PHP')) { - mt_srand(0xdeadbeef, MT_RAND_PHP); - } else { - mt_srand(0xdeadbeef); - } + mt_srand(0xdeadbeef, MT_RAND_PHP); - for ($i = 0; $i < 10; $i++) { - $array = new BitArray(mt_rand(1, 100)); + for ($i = 0; $i < 10; ++$i) { + $array = new BitArray(mt_rand(1, 100)); $numSet = mt_rand(0, 19); - for ($j = 0; $j < $numSet; $j++) { + for ($j = 0; $j < $numSet; ++$j) { $array->set(mt_rand(0, $array->getSize() - 1)); } $numQueries = mt_rand(0, 19); - for ($j = 0; $j < $numQueries; $j++) { - $query = mt_rand(0, $array->getSize() - 1); + for ($j = 0; $j < $numQueries; ++$j) { + $query = mt_rand(0, $array->getSize() - 1); $expected = $query; - while ($expected < $array->getSize() && !$array->get($expected)) { - $expected++; + while ($expected < $array->getSize() && ! $array->get($expected)) { + ++$expected; } $actual = $array->getNextSet($query); @@ -129,36 +120,36 @@ public function testGetNextSet5() } } - public function testSetBulk() + public function testSetBulk() : void { $array = new BitArray(64); $array->setBulk(32, 0xFFFF0000); - for ($i = 0; $i < 48; $i++) { + for ($i = 0; $i < 48; ++$i) { $this->assertFalse($array->get($i)); } - for ($i = 48; $i < 64; $i++) { + for ($i = 48; $i < 64; ++$i) { $this->assertTrue($array->get($i)); } } - public function testClear() + public function testClear() : void { $array = new BitArray(32); - for ($i = 0; $i < 32; $i++) { + for ($i = 0; $i < 32; ++$i) { $array->set($i); } $array->clear(); - for ($i = 0; $i < 32; $i++) { + for ($i = 0; $i < 32; ++$i) { $this->assertFalse($array->get($i)); } } - public function testGetArray() + public function testGetArray() : void { $array = new BitArray(64); $array->set(0); @@ -166,11 +157,11 @@ public function testGetArray() $ints = $array->getBitArray(); - $this->assertEquals(1, $ints[0]); - $this->assertEquals(0x80000000, $ints[1]); + $this->assertSame(1, $ints[0]); + $this->assertSame(0x80000000, $ints[1]); } - public function testIsRange() + public function testIsRange() : void { $array = new BitArray(64); $this->assertTrue($array->isRange(0, 64, false)); @@ -185,17 +176,17 @@ public function testIsRange() $array->set(34); $this->assertFalse($array->isRange(31, 35, true)); - for ($i = 0; $i < 31; $i++) { + for ($i = 0; $i < 31; ++$i) { $array->set($i); } $this->assertTrue($array->isRange(0, 33, true)); - for ($i = 33; $i < 64; $i++) { + for ($i = 33; $i < 64; ++$i) { $array->set($i); } $this->assertTrue($array->isRange(0, 64, true)); $this->assertFalse($array->isRange(0, 64, false)); } -} \ No newline at end of file +} diff --git a/tests/BaconQrCode/Common/BitMatrixTest.php b/test/Common/BitMatrixTest.php similarity index 50% rename from tests/BaconQrCode/Common/BitMatrixTest.php rename to test/Common/BitMatrixTest.php index 89a5881..8ad86d4 100644 --- a/tests/BaconQrCode/Common/BitMatrixTest.php +++ b/test/Common/BitMatrixTest.php @@ -1,25 +1,21 @@ assertEquals(33, $matrix->getHeight()); - for ($y = 0; $y < 33; $y++) { - for ($x = 0; $x < 33; $x++) { + for ($y = 0; $y < 33; ++$y) { + for ($x = 0; $x < 33; ++$x) { if ($y * $x % 3 === 0) { $matrix->set($x, $y); } @@ -27,29 +23,29 @@ public function testGetSet() } for ($y = 0; $y < 33; $y++) { - for ($x = 0; $x < 33; $x++) { - $this->assertEquals($x * $y % 3 === 0, $matrix->get($x, $y)); + for ($x = 0; $x < 33; ++$x) { + $this->assertSame(0 === $x * $y % 3, $matrix->get($x, $y)); } } } - public function testSetRegion() + public function testSetRegion() : void { $matrix = new BitMatrix(5); $matrix->setRegion(1, 1, 3, 3); - for ($y = 0; $y < 5; $y++) { - for ($x = 0; $x < 5; $x++) { - $this->assertEquals($y >= 1 && $y <= 3 && $x >= 1 && $x <= 3, $matrix->get($x, $y)); + for ($y = 0; $y < 5; ++$y) { + for ($x = 0; $x < 5; ++$x) { + $this->assertSame($y >= 1 && $y <= 3 && $x >= 1 && $x <= 3, $matrix->get($x, $y)); } } } - public function testRectangularMatrix() + public function testRectangularMatrix() : void { $matrix = new BitMatrix(75, 20); - $this->assertEquals(75, $matrix->getWidth()); - $this->assertEquals(20, $matrix->getHeight()); + $this->assertSame(75, $matrix->getWidth()); + $this->assertSame(20, $matrix->getHeight()); $matrix->set(10, 0); $matrix->set(11, 1); @@ -72,48 +68,48 @@ public function testRectangularMatrix() $this->assertFalse($matrix->get(51, 3)); } - public function testRectangularSetRegion() + public function testRectangularSetRegion() : void { $matrix = new BitMatrix(320, 240); - $this->assertEquals(320, $matrix->getWidth()); - $this->assertEquals(240, $matrix->getHeight()); + $this->assertSame(320, $matrix->getWidth()); + $this->assertSame(240, $matrix->getHeight()); $matrix->setRegion(105, 22, 80, 12); - for ($y = 0; $y < 240; $y++) { - for ($x = 0; $x < 320; $x++) { + for ($y = 0; $y < 240; ++$y) { + for ($x = 0; $x < 320; ++$x) { $this->assertEquals($y >= 22 && $y < 34 && $x >= 105 && $x < 185, $matrix->get($x, $y)); } } } - public function testGetRow() + public function testGetRow() : void { $matrix = new BitMatrix(102, 5); - for ($x = 0; $x < 102; $x++) { - if ($x & 3 === 0) { + for ($x = 0; $x < 102; ++$x) { + if (0 === ($x & 3)) { $matrix->set($x, 2); } } $array1 = $matrix->getRow(2, null); - $this->assertEquals(102, $array1->getSize()); + $this->assertSame(102, $array1->getSize()); $array2 = new BitArray(60); $array2 = $matrix->getRow(2, $array2); - $this->assertEquals(102, $array2->getSize()); + $this->assertSame(102, $array2->getSize()); $array3 = new BitArray(200); $array3 = $matrix->getRow(2, $array3); - $this->assertEquals(200, $array3->getSize()); + $this->assertSame(200, $array3->getSize()); - for ($x = 0; $x < 102; $x++) { - $on = ($x & 3 === 0); + for ($x = 0; $x < 102; ++$x) { + $on = (0 === ($x & 3)); - $this->assertEquals($on, $array1->get($x)); - $this->assertEquals($on, $array2->get($x)); - $this->assertEquals($on, $array3->get($x)); + $this->assertSame($on, $array1->get($x)); + $this->assertSame($on, $array2->get($x)); + $this->assertSame($on, $array3->get($x)); } } -} \ No newline at end of file +} diff --git a/test/Common/BitUtilsTest.php b/test/Common/BitUtilsTest.php new file mode 100644 index 0000000..2904d31 --- /dev/null +++ b/test/Common/BitUtilsTest.php @@ -0,0 +1,25 @@ +assertSame(1, BitUtils::unsignedRightShift(1, 0)); + $this->assertSame(1, BitUtils::unsignedRightShift(10, 3)); + $this->assertSame(536870910, BitUtils::unsignedRightShift(-10, 3)); + } + + public function testNumberOfTrailingZeros() : void + { + $this->assertSame(32, BitUtils::numberOfTrailingZeros(0)); + $this->assertSame(1, BitUtils::numberOfTrailingZeros(10)); + $this->assertSame(0, BitUtils::numberOfTrailingZeros(15)); + $this->assertSame(2, BitUtils::numberOfTrailingZeros(20)); + } +} diff --git a/test/Common/ErrorCorrectionLevelTest.php b/test/Common/ErrorCorrectionLevelTest.php new file mode 100644 index 0000000..369b5d9 --- /dev/null +++ b/test/Common/ErrorCorrectionLevelTest.php @@ -0,0 +1,25 @@ +assertSame(0x0, ErrorCorrectionLevel::M()->getBits()); + $this->assertSame(0x1, ErrorCorrectionLevel::L()->getBits()); + $this->assertSame(0x2, ErrorCorrectionLevel::H()->getBits()); + $this->assertSame(0x3, ErrorCorrectionLevel::Q()->getBits()); + } + + public function testInvalidErrorCorrectionLevelThrowsException() : void + { + $this->expectException(OutOfBoundsException::class); + ErrorCorrectionLevel::forBits(4); + } +} diff --git a/test/Common/FormatInformationTest.php b/test/Common/FormatInformationTest.php new file mode 100644 index 0000000..39534a2 --- /dev/null +++ b/test/Common/FormatInformationTest.php @@ -0,0 +1,94 @@ +assertSame(0, FormatInformation::numBitsDiffering(1, 1)); + $this->assertSame(1, FormatInformation::numBitsDiffering(0, 2)); + $this->assertSame(2, FormatInformation::numBitsDiffering(1, 2)); + $this->assertEquals(32, FormatInformation::numBitsDiffering(-1, 0)); + } + + public function testDecode() : void + { + $expected = FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ); + + $this->assertNotNull($expected); + $this->assertSame(7, $expected->getDataMask()); + $this->assertSame(ErrorCorrectionLevel::Q(), $expected->getErrorCorrectionLevel()); + + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::UNMAKSED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ) + ); + } + + public function testDecodeWithBitDifference() : void + { + $expected = FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ); + + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x1, + self::MASKED_TEST_FORMAT_INFO ^ 0x1 + ) + ); + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x3, + self::MASKED_TEST_FORMAT_INFO ^ 0x3 + ) + ); + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x7, + self::MASKED_TEST_FORMAT_INFO ^ 0x7 + ) + ); + $this->assertNull( + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0xf, + self::MASKED_TEST_FORMAT_INFO ^ 0xf + ) + ); + } + + public function testDecodeWithMisRead() : void + { + $expected = FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ); + + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x3, + self::MASKED_TEST_FORMAT_INFO ^ 0xf + ) + ); + } +} diff --git a/test/Common/ModeTest.php b/test/Common/ModeTest.php new file mode 100644 index 0000000..51fcb3e --- /dev/null +++ b/test/Common/ModeTest.php @@ -0,0 +1,19 @@ +assertSame(0x0, Mode::TERMINATOR()->getBits()); + $this->assertSame(0x1, Mode::NUMERIC()->getBits()); + $this->assertSame(0x2, Mode::ALPHANUMERIC()->getBits()); + $this->assertSame(0x4, Mode::BYTE()->getBits()); + $this->assertSame(0x8, Mode::KANJI()->getBits()); + } +} diff --git a/test/Common/ReedSolomonCodecTest.php b/test/Common/ReedSolomonCodecTest.php new file mode 100644 index 0000000..47975b5 --- /dev/null +++ b/test/Common/ReedSolomonCodecTest.php @@ -0,0 +1,96 @@ +encode($block, $parity); + + // Copy parity into test blocks + for ($i = 0; $i < $numRoots; ++$i) { + $block[$i + $dataSize] = $parity[$i]; + $tBlock[$i + $dataSize] = $parity[$i]; + } + + // Seed with errors + for ($i = 0; $i < $errors; ++$i) { + $errorValue = mt_rand(1, $blockSize); + + do { + $errorLocation = mt_rand(0, $blockSize); + } while (0 !== $errorLocations[$errorLocation]); + + $errorLocations[$errorLocation] = 1; + + if (mt_rand(0, 1)) { + $erasures[] = $errorLocation; + } + + $tBlock[$errorLocation] ^= $errorValue; + } + + $erasures = SplFixedArray::fromArray($erasures, false); + + // Decode the errored block + $foundErrors = $codec->decode($tBlock, $erasures); + + if ($errors > 0 && null === $foundErrors) { + $this->assertSame($block, $tBlock, 'Decoder failed to correct errors'); + } + + $this->assertSame($errors, $foundErrors, 'Found errors do not equal expected errors'); + + for ($i = 0; $i < $foundErrors; ++$i) { + if (0 === $errorLocations[$erasures[$i]]) { + $this->fail(sprintf('Decoder indicates error in location %d without error', $erasures[$i])); + } + } + + $this->assertEquals($block, $tBlock, 'Decoder did not correct errors'); + } + } +} diff --git a/test/Common/VersionTest.php b/test/Common/VersionTest.php new file mode 100644 index 0000000..f6f038b --- /dev/null +++ b/test/Common/VersionTest.php @@ -0,0 +1,78 @@ +assertNotNull($version); + $this->assertEquals($versionNumber, $version->getVersionNumber()); + $this->assertNotNull($version->getAlignmentPatternCenters()); + + if ($versionNumber > 1) { + $this->assertTrue(count($version->getAlignmentPatternCenters()) > 0); + } + + $this->assertEquals($dimension, $version->getDimensionForVersion()); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::H())); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::L())); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::M())); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::Q())); + $this->assertNotNull($version->buildFunctionPattern()); + } + + /** + * @dataProvider versions + */ + public function testGetProvisionalVersionForDimension(int $versionNumber, int $dimension) : void + { + $this->assertSame( + $versionNumber, + Version::getProvisionalVersionForDimension($dimension)->getVersionNumber() + ); + } + + /** + * @dataProvider decodeInformation + */ + public function testDecodeVersionInformation(int $expectedVersion, int $mask) : void + { + $version = Version::decodeVersionInformation($mask); + $this->assertNotNull($version); + $this->assertSame($expectedVersion, $version->getVersionNumber()); + } +} diff --git a/test/Encoder/EncoderTest.php b/test/Encoder/EncoderTest.php new file mode 100644 index 0000000..9baa66b --- /dev/null +++ b/test/Encoder/EncoderTest.php @@ -0,0 +1,487 @@ +getMethods(ReflectionMethod::IS_STATIC) as $method) { + $method->setAccessible(true); + $this->methods[$method->getName()] = $method; + } + } + + public function testGetAlphanumericCode() : void + { + // The first ten code points are numbers. + for ($i = 0; $i < 10; ++$i) { + $this->assertSame($i, $this->methods['getAlphanumericCode']->invoke(null, ord('0') + $i)); + } + + // The next 26 code points are capital alphabet letters. + for ($i = 10; $i < 36; ++$i) { + // The first ten code points are numbers + $this->assertSame($i, $this->methods['getAlphanumericCode']->invoke(null, ord('A') + $i - 10)); + } + + // Others are symbol letters. + $this->assertSame(36, $this->methods['getAlphanumericCode']->invoke(null, ord(' '))); + $this->assertSame(37, $this->methods['getAlphanumericCode']->invoke(null, ord('$'))); + $this->assertSame(38, $this->methods['getAlphanumericCode']->invoke(null, ord('%'))); + $this->assertSame(39, $this->methods['getAlphanumericCode']->invoke(null, ord('*'))); + $this->assertSame(40, $this->methods['getAlphanumericCode']->invoke(null, ord('+'))); + $this->assertSame(41, $this->methods['getAlphanumericCode']->invoke(null, ord('-'))); + $this->assertSame(42, $this->methods['getAlphanumericCode']->invoke(null, ord('.'))); + $this->assertSame(43, $this->methods['getAlphanumericCode']->invoke(null, ord('/'))); + $this->assertSame(44, $this->methods['getAlphanumericCode']->invoke(null, ord(':'))); + + // Should return -1 for other letters. + $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord('a'))); + $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord('#'))); + $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord("\0"))); + } + + public function testChooseMode() : void + { + // Numeric mode + $this->assertSame(Mode::NUMERIC(), $this->methods['chooseMode']->invoke(null, '0')); + $this->assertSame(Mode::NUMERIC(), $this->methods['chooseMode']->invoke(null, '0123456789')); + + // Alphanumeric mode + $this->assertSame(Mode::ALPHANUMERIC(), $this->methods['chooseMode']->invoke(null, 'A')); + $this->assertSame( + Mode::ALPHANUMERIC(), + $this->methods['chooseMode']->invoke(null, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:') + ); + + // 8-bit byte mode + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, 'a')); + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, '#')); + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, '')); + + // AIUE in Hiragana in SHIFT-JIS + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\x8\xa\x8\xa\x8\xa\x8\xa6")); + + // Nihon in Kanji in SHIFT-JIS + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\x9\xf\x9\x7b")); + + // Sou-Utso-Byou in Kanji in SHIFT-JIS + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\xe\x4\x9\x5\x9\x61")); + } + + public function testEncode() : void + { + $qrCode = Encoder::encode('ABCDEF', ErrorCorrectionLevel::H()); + $expected = "<<\n" + . " mode: ALPHANUMERIC\n" + . " ecLevel: H\n" + . " version: 1\n" + . " maskPattern: 0\n" + . " matrix:\n" + . " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0\n" + . " 0 0 1 0 1 1 1 0 1 1 0 0 1 1 0 0 0 1 0 0 1\n" + . " 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0\n" + . " 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 1 0\n" + . " 1 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 0\n" + . " 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0\n" + . " 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0\n" + . " 1 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1\n" + . " 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 1 0 1 0\n" + . " 1 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 1\n" + . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1\n" + . ">>\n"; + + $this->assertSame($expected, (string) $qrCode); + } + + public function testSimpleUtf8Eci() : void + { + $qrCode = Encoder::encode('hello', ErrorCorrectionLevel::H(), 'utf-8'); + $expected = "<<\n" + . " mode: BYTE\n" + . " ecLevel: H\n" + . " version: 1\n" + . " maskPattern: 3\n" + . " matrix:\n" + . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 1 1 0 0 1 1 1 1 0 0 0 1 1 0 1 0 0 0 0\n" + . " 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0 1 0 1 1 1 0\n" + . " 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 1 1 1\n" + . " 1 1 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 1 0\n" + . " 0 0 0 0 1 0 1 1 1 1 0 0 0 0 0 1 0 0 1 0 0\n" + . " 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 0 0 1 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 0\n" + . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 0 0 1 1 0 0 0\n" + . " 1 0 1 1 1 0 1 0 1 1 0 0 0 1 0 0 1 0 0 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 1 0 1 1 0\n" + . " 1 1 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 0 0 0 0\n" + . ">>\n"; + + $this->assertSame($expected, (string) $qrCode); + } + + public function testAppendModeInfo() : void + { + $bits = new BitArray(); + $this->methods['appendModeInfo']->invoke(null, Mode::NUMERIC(), $bits); + $this->assertSame(' ...X', (string) $bits); + } + + public function testAppendLengthInfo() : void + { + // 1 letter (1/1), 10 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 1, + Version::getVersionForNumber(1), + Mode::NUMERIC(), + $bits + ); + $this->assertSame(' ........ .X', (string) $bits); + + // 2 letters (2/1), 11 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 2, + Version::getVersionForNumber(10), + Mode::ALPHANUMERIC(), + $bits + ); + $this->assertSame(' ........ .X.', (string) $bits); + + // 255 letters (255/1), 16 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 255, + Version::getVersionForNumber(27), + Mode::BYTE(), + $bits + ); + $this->assertSame(' ........ XXXXXXXX', (string) $bits); + + // 512 letters (1024/2), 12 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 512, + Version::getVersionForNumber(40), + Mode::KANJI(), + $bits + ); + $this->assertSame(' ..X..... ....', (string) $bits); + } + + public function testAppendBytes() : void + { + // Should use appendNumericBytes. + // 1 = 01 = 0001 in 4 bits. + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + '1', + Mode::NUMERIC(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' ...X', (string) $bits); + + // Should use appendAlphaNumericBytes. + // A = 10 = 0xa = 001010 in 6 bits. + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + 'A', + Mode::ALPHANUMERIC(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' ..X.X.', (string) $bits); + + // Should use append8BitBytes. + // 0x61, 0x62, 0x63 + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + 'abc', + Mode::BYTE(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' .XX....X .XX...X. .XX...XX', (string) $bits); + + // Should use appendKanjiBytes. + // 0x93, 0x5f + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + "\x93\x5f", + Mode::KANJI(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' .XX.XX.. XXXXX', (string) $bits); + + // Lower letters such as 'a' cannot be encoded in alphanumeric mode. + $this->expectException(WriterException::class); + $this->methods['appendBytes']->invoke( + null, + 'a', + Mode::ALPHANUMERIC(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + } + + public function testTerminateBits() : void + { + $bits = new BitArray(); + $this->methods['terminateBits']->invoke(null, 0, $bits); + $this->assertSame('', (string) $bits); + + $bits = new BitArray(); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 3); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 5); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 8); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $this->methods['terminateBits']->invoke(null, 2, $bits); + $this->assertSame(' ........ XXX.XX..', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 1); + $this->methods['terminateBits']->invoke(null, 3, $bits); + $this->assertSame(' ........ XXX.XX.. ...X...X', (string) $bits); + } + + public function testGetNumDataBytesAndNumEcBytesForBlockId() : void + { + // Version 1-H. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 26, 9, 1, 0); + $this->assertSame(9, $numDataBytes); + $this->assertSame(17, $numEcBytes); + + // Version 3-H. 2 blocks. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 70, 26, 2, 0); + $this->assertSame(13, $numDataBytes); + $this->assertSame(22, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 70, 26, 2, 1); + $this->assertSame(13, $numDataBytes); + $this->assertSame(22, $numEcBytes); + + // Version 7-H. (4 + 1) blocks. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 196, 66, 5, 0); + $this->assertSame(13, $numDataBytes); + $this->assertSame(26, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 196, 66, 5, 4); + $this->assertSame(14, $numDataBytes); + $this->assertSame(26, $numEcBytes); + + // Version 40-H. (20 + 61) blocks. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 3706, 1276, 81, 0); + $this->assertSame(15, $numDataBytes); + $this->assertSame(30, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 3706, 1276, 81, 20); + $this->assertSame(16, $numDataBytes); + $this->assertSame(30, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 3706, 1276, 81, 80); + $this->assertSame(16, $numDataBytes); + $this->assertSame(30, $numEcBytes); + } + + public function testInterleaveWithEcBytes() : void + { + $dataBytes = SplFixedArray::fromArray([32, 65, 205, 69, 41, 220, 46, 128, 236], false); + $in = new BitArray(); + + foreach ($dataBytes as $dataByte) { + $in->appendBits($dataByte, 8); + } + + $outBits = $this->methods['interleaveWithEcBytes']->invoke(null, $in, 26, 9, 1); + $expected = SplFixedArray::fromArray([ + // Data bytes. + 32, 65, 205, 69, 41, 220, 46, 128, 236, + // Error correction bytes. + 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61, + ], false); + + $out = $outBits->toBytes(0, count($expected)); + + $this->assertEquals($expected, $out); + } + + public function testAppendNumericBytes() : void + { + // 1 = 01 = 0001 in 4 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '1', $bits); + $this->assertSame(' ...X', (string) $bits); + + // 12 = 0xc = 0001100 in 7 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '12', $bits); + $this->assertSame(' ...XX..', (string) $bits); + + // 123 = 0x7b = 0001111011 in 10 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '123', $bits); + $this->assertSame(' ...XXXX. XX', (string) $bits); + + // 1234 = "123" + "4" = 0001111011 + 0100 in 14 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '1234', $bits); + $this->assertSame(' ...XXXX. XX.X..', (string) $bits); + + // Empty + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '', $bits); + $this->assertSame('', (string) $bits); + } + + public function testAppendAlphanumericBytes() : void + { + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'A', $bits); + $this->assertSame(' ..X.X.', (string) $bits); + + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'AB', $bits); + $this->assertSame(' ..XXX..X X.X', (string) $bits); + + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'ABC', $bits); + $this->assertSame(' ..XXX..X X.X..XX. .', (string) $bits); + + // Empty + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, '', $bits); + $this->assertSame('', (string) $bits); + + // Invalid data + $this->expectException(WriterException::class); + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'abc', $bits); + } + + public function testAppend8BitBytes() : void + { + // 0x61, 0x62, 0x63 + $bits = new BitArray(); + $this->methods['append8BitBytes']->invoke(null, 'abc', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); + $this->assertSame(' .XX....X .XX...X. .XX...XX', (string) $bits); + + // Empty + $bits = new BitArray(); + $this->methods['append8BitBytes']->invoke(null, '', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); + $this->assertSame('', (string) $bits); + } + + public function testAppendKanjiBytes() : void + { + // Numbers are from page 21 of JISX0510:2004 + $bits = new BitArray(); + $this->methods['appendKanjiBytes']->invoke(null, "\x93\x5f", $bits); + $this->assertSame(' .XX.XX.. XXXXX', (string) $bits); + + $this->methods['appendKanjiBytes']->invoke(null, "\xe4\xaa", $bits); + $this->assertSame(' .XX.XX.. XXXXXXX. X.X.X.X. X.', (string) $bits); + } + + public function testGenerateEcBytes() : void + { + // Numbers are from http://www.swetake.com/qr/qr3.html and + // http://www.swetake.com/qr/qr9.html + $dataBytes = SplFixedArray::fromArray([32, 65, 205, 69, 41, 220, 46, 128, 236], false); + $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); + $expected = SplFixedArray::fromArray( + [42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61], + false + ); + $this->assertEquals($expected, $ecBytes); + + $dataBytes = SplFixedArray::fromArray( + [67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214], + false + ); + $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 18); + $expected = SplFixedArray::fromArray( + [175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187], + false + ); + $this->assertEquals($expected, $ecBytes); + + // High-order zero coefficient case. + $dataBytes = SplFixedArray::fromArray([32, 49, 205, 69, 42, 20, 0, 236, 17], false); + $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); + $expected = SplFixedArray::fromArray( + [0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213], + false + ); + $this->assertEquals($expected, $ecBytes); + } +} diff --git a/test/Encoder/MaskUtilTest.php b/test/Encoder/MaskUtilTest.php new file mode 100644 index 0000000..46670fc --- /dev/null +++ b/test/Encoder/MaskUtilTest.php @@ -0,0 +1,251 @@ +assertSame( + 1 === $expected[$y][$x], + MaskUtil::getDataMaskBit($maskPattern, $x, $y) + ); + } + } + } + + public function testApplyMaskPenaltyRule1() : void + { + $matrix = new ByteMatrix(4, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(3, 0, 0); + + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule1($matrix)); + + // Horizontal + $matrix = new ByteMatrix(6, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(3, 0, 0); + $matrix->set(4, 0, 0); + $matrix->set(5, 0, 1); + $this->assertSame(3, MaskUtil::applyMaskPenaltyRule1($matrix)); + $matrix->set(5, 0, 0); + $this->assertSame(4, MaskUtil::applyMaskPenaltyRule1($matrix)); + + // Vertical + $matrix = new ByteMatrix(1, 6); + $matrix->set(0, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(0, 2, 0); + $matrix->set(0, 3, 0); + $matrix->set(0, 4, 0); + $matrix->set(0, 5, 1); + $this->assertSame(3, MaskUtil::applyMaskPenaltyRule1($matrix)); + $matrix->set(0, 5, 0); + $this->assertSame(4, MaskUtil::applyMaskPenaltyRule1($matrix)); + } + + public function testApplyMaskPenaltyRule2() : void + { + $matrix = new ByteMatrix(1, 1); + $matrix->set(0, 0, 0); + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule2($matrix)); + + $matrix = new ByteMatrix(2, 2); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(1, 1, 1); + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule2($matrix)); + + $matrix = new ByteMatrix(2, 2); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(1, 1, 0); + $this->assertSame(3, MaskUtil::applyMaskPenaltyRule2($matrix)); + + $matrix = new ByteMatrix(3, 3); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(1, 1, 0); + $matrix->set(2, 1, 0); + $matrix->set(0, 2, 0); + $matrix->set(1, 2, 0); + $matrix->set(2, 2, 0); + $this->assertSame(3 * 4, MaskUtil::applyMaskPenaltyRule2($matrix)); + } + + public function testApplyMaskPenalty3() : void + { + // Horizontal 00001011101 + $matrix = new ByteMatrix(11, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(3, 0, 0); + $matrix->set(4, 0, 1); + $matrix->set(5, 0, 0); + $matrix->set(6, 0, 1); + $matrix->set(7, 0, 1); + $matrix->set(8, 0, 1); + $matrix->set(9, 0, 0); + $matrix->set(10, 0, 1); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + + // Horizontal 10111010000 + $matrix = new ByteMatrix(11, 1); + $matrix->set(0, 0, 1); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 1); + $matrix->set(3, 0, 1); + $matrix->set(4, 0, 1); + $matrix->set(5, 0, 0); + $matrix->set(6, 0, 1); + $matrix->set(7, 0, 0); + $matrix->set(8, 0, 0); + $matrix->set(9, 0, 0); + $matrix->set(10, 0, 0); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + + // Vertical 00001011101 + $matrix = new ByteMatrix(1, 11); + $matrix->set(0, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(0, 2, 0); + $matrix->set(0, 3, 0); + $matrix->set(0, 4, 1); + $matrix->set(0, 5, 0); + $matrix->set(0, 6, 1); + $matrix->set(0, 7, 1); + $matrix->set(0, 8, 1); + $matrix->set(0, 9, 0); + $matrix->set(0, 10, 1); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + + // Vertical 10111010000 + $matrix = new ByteMatrix(1, 11); + $matrix->set(0, 0, 1); + $matrix->set(0, 1, 0); + $matrix->set(0, 2, 1); + $matrix->set(0, 3, 1); + $matrix->set(0, 4, 1); + $matrix->set(0, 5, 0); + $matrix->set(0, 6, 1); + $matrix->set(0, 7, 0); + $matrix->set(0, 8, 0); + $matrix->set(0, 9, 0); + $matrix->set(0, 10, 0); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + } + + public function testApplyMaskPenaltyRule4() : void + { + // Dark cell ratio = 0% + $matrix = new ByteMatrix(1, 1); + $matrix->set(0, 0, 0); + $this->assertSame(100, MaskUtil::applyMaskPenaltyRule4($matrix)); + + // Dark cell ratio = 5% + $matrix = new ByteMatrix(2, 1); + $matrix->set(0, 0, 0); + $matrix->set(0, 0, 1); + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule4($matrix)); + + // Dark cell ratio = 66.67% + $matrix = new ByteMatrix(6, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 1); + $matrix->set(2, 0, 1); + $matrix->set(3, 0, 1); + $matrix->set(4, 0, 1); + $matrix->set(5, 0, 0); + $this->assertSame(30, MaskUtil::applyMaskPenaltyRule4($matrix)); + } +} diff --git a/tests/BaconQrCode/Encoder/MatrixUtilTest.php b/test/Encoder/MatrixUtilTest.php similarity index 78% rename from tests/BaconQrCode/Encoder/MatrixUtilTest.php rename to test/Encoder/MatrixUtilTest.php index bf3544f..106ceaa 100644 --- a/tests/BaconQrCode/Encoder/MatrixUtilTest.php +++ b/test/Encoder/MatrixUtilTest.php @@ -1,29 +1,28 @@ getMethods(ReflectionMethod::IS_STATIC) as $method) { $method->setAccessible(true); @@ -31,9 +30,9 @@ public function setUp() } } - public function testToString() + public function testToString() : void { - $matrix= new ByteMatrix(3, 3); + $matrix = new ByteMatrix(3, 3); $matrix->set(0, 0, 0); $matrix->set(1, 0, 1); $matrix->set(2, 0, 0); @@ -45,21 +44,21 @@ public function testToString() $matrix->set(2, 2, -1); $expected = " 0 1 0\n 1 0 1\n \n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testClearMatrix() + public function testClearMatrix() : void { $matrix = new ByteMatrix(2, 2); MatrixUtil::clearMatrix($matrix); - $this->assertEquals(-1, $matrix->get(0, 0)); - $this->assertEquals(-1, $matrix->get(1, 0)); - $this->assertEquals(-1, $matrix->get(0, 1)); - $this->assertEquals(-1, $matrix->get(1, 1)); + $this->assertSame(-1, $matrix->get(0, 0)); + $this->assertSame(-1, $matrix->get(1, 0)); + $this->assertSame(-1, $matrix->get(0, 1)); + $this->assertSame(-1, $matrix->get(1, 1)); } - public function testEmbedBasicPatterns1() + public function testEmbedBasicPatterns1() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); @@ -90,10 +89,10 @@ public function testEmbedBasicPatterns1() . " 1 0 0 0 0 0 1 0 \n" . " 1 1 1 1 1 1 1 0 \n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testEmbedBasicPatterns2() + public function testEmbedBasicPatterns2() : void { $matrix = new ByteMatrix(25, 25); MatrixUtil::clearMatrix($matrix); @@ -128,16 +127,16 @@ public function testEmbedBasicPatterns2() . " 1 0 0 0 0 0 1 0 \n" . " 1 1 1 1 1 1 1 0 \n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testEmbedTypeInfo() + public function testEmbedTypeInfo() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); $this->methods['embedTypeInfo']->invoke( null, - new ErrorCorrectionLevel(ErrorCorrectionLevel::M), + ErrorCorrectionLevel::M(), 5, $matrix ); @@ -163,10 +162,10 @@ public function testEmbedTypeInfo() . " 0 \n" . " 1 \n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testEmbedVersionInfo() + public function testEmbedVersionInfo() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); @@ -197,10 +196,10 @@ public function testEmbedVersionInfo() . " \n" . " \n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testEmbedDataBits() + public function testEmbedDataBits() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); @@ -240,15 +239,15 @@ public function testEmbedDataBits() . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testBuildMatrix() + public function testBuildMatrix() : void { - $bytes = array( + $bytes = [ 32, 65, 205, 69, 41, 220, 46, 128, 236, 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219 , 61 - ); + ]; $bits = new BitArray(); foreach ($bytes as $byte) { @@ -258,7 +257,7 @@ public function testBuildMatrix() $matrix = new ByteMatrix(21, 21); MatrixUtil::buildMatrix( $bits, - new ErrorCorrectionLevel(ErrorCorrectionLevel::H), + ErrorCorrectionLevel::H(), Version::getVersionForNumber(1), 3, $matrix @@ -286,51 +285,51 @@ public function testBuildMatrix() . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0\n" . " 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 1 0 0 1 0\n"; - $this->assertEquals($expected, $matrix->__toString()); + $this->assertSame($expected, (string) $matrix); } - public function testFindMsbSet() + public function testFindMsbSet() : void { - $this->assertEquals(0, $this->methods['findMsbSet']->invoke(null, 0)); - $this->assertEquals(1, $this->methods['findMsbSet']->invoke(null, 1)); - $this->assertEquals(8, $this->methods['findMsbSet']->invoke(null, 0x80)); - $this->assertEquals(32, $this->methods['findMsbSet']->invoke(null, 0x80000000)); + $this->assertSame(0, $this->methods['findMsbSet']->invoke(null, 0)); + $this->assertSame(1, $this->methods['findMsbSet']->invoke(null, 1)); + $this->assertSame(8, $this->methods['findMsbSet']->invoke(null, 0x80)); + $this->assertSame(32, $this->methods['findMsbSet']->invoke(null, 0x80000000)); } - public function testCalculateBchCode() + public function testCalculateBchCode() : void { // Encoding of type information. // From Appendix C in JISX0510:2004 (p 65) - $this->assertEquals(0xdc, $this->methods['calculateBchCode']->invoke(null, 5, 0x537)); + $this->assertSame(0xdc, $this->methods['calculateBchCode']->invoke(null, 5, 0x537)); // From http://www.swetake.com/qr/qr6.html - $this->assertEquals(0x1c2, $this->methods['calculateBchCode']->invoke(null, 0x13, 0x537)); + $this->assertSame(0x1c2, $this->methods['calculateBchCode']->invoke(null, 0x13, 0x537)); // From http://www.swetake.com/qr/qr11.html - $this->assertEquals(0x214, $this->methods['calculateBchCode']->invoke(null, 0x1b, 0x537)); + $this->assertSame(0x214, $this->methods['calculateBchCode']->invoke(null, 0x1b, 0x537)); // Encoding of version information. // From Appendix D in JISX0510:2004 (p 68) - $this->assertEquals(0xc94, $this->methods['calculateBchCode']->invoke(null, 7, 0x1f25)); - $this->assertEquals(0x5bc, $this->methods['calculateBchCode']->invoke(null, 8, 0x1f25)); - $this->assertEquals(0xa99, $this->methods['calculateBchCode']->invoke(null, 9, 0x1f25)); - $this->assertEquals(0x4d3, $this->methods['calculateBchCode']->invoke(null, 10, 0x1f25)); - $this->assertEquals(0x9a6, $this->methods['calculateBchCode']->invoke(null, 20, 0x1f25)); - $this->assertEquals(0xd75, $this->methods['calculateBchCode']->invoke(null, 30, 0x1f25)); - $this->assertEquals(0xc69, $this->methods['calculateBchCode']->invoke(null, 40, 0x1f25)); + $this->assertSame(0xc94, $this->methods['calculateBchCode']->invoke(null, 7, 0x1f25)); + $this->assertSame(0x5bc, $this->methods['calculateBchCode']->invoke(null, 8, 0x1f25)); + $this->assertSame(0xa99, $this->methods['calculateBchCode']->invoke(null, 9, 0x1f25)); + $this->assertSame(0x4d3, $this->methods['calculateBchCode']->invoke(null, 10, 0x1f25)); + $this->assertSame(0x9a6, $this->methods['calculateBchCode']->invoke(null, 20, 0x1f25)); + $this->assertSame(0xd75, $this->methods['calculateBchCode']->invoke(null, 30, 0x1f25)); + $this->assertSame(0xc69, $this->methods['calculateBchCode']->invoke(null, 40, 0x1f25)); } - public function testMakeVersionInfoBits() + public function testMakeVersionInfoBits() : void { // From Appendix D in JISX0510:2004 (p 68) $bits = new BitArray(); $this->methods['makeVersionInfoBits']->invoke(null, Version::getVersionForNumber(7), $bits); - $this->assertEquals(' ...XXXXX ..X..X.X ..', $bits->__toString()); + $this->assertSame(' ...XXXXX ..X..X.X ..', (string) $bits); } - public function testMakeTypeInfoBits() + public function testMakeTypeInfoBits() : void { // From Appendix D in JISX0510:2004 (p 68) $bits = new BitArray(); - $this->methods['makeTypeInfoBits']->invoke(null, new ErrorCorrectionLevel(ErrorCorrectionLevel::M), 5, $bits); - $this->assertEquals(' X......X X..XXX.', $bits->__toString()); + $this->methods['makeTypeInfoBits']->invoke(null, ErrorCorrectionLevel::M(), 5, $bits); + $this->assertSame(' X......X X..XXX.', (string) $bits); } -} \ No newline at end of file +} diff --git a/tests/BaconQrCode/Common/BitUtilsTest.php b/tests/BaconQrCode/Common/BitUtilsTest.php deleted file mode 100644 index b80ff7d..0000000 --- a/tests/BaconQrCode/Common/BitUtilsTest.php +++ /dev/null @@ -1,30 +0,0 @@ -assertEquals(1, BitUtils::unsignedRightShift(1, 0)); - $this->assertEquals(1, BitUtils::unsignedRightShift(10, 3)); - $this->assertEquals(536870910, BitUtils::unsignedRightShift(-10, 3)); - } - - public function testNumberOfTrailingZeros() - { - $this->assertEquals(32, BitUtils::numberOfTrailingZeros(0)); - $this->assertEquals(1, BitUtils::numberOfTrailingZeros(10)); - $this->assertEquals(0, BitUtils::numberOfTrailingZeros(15)); - $this->assertEquals(2, BitUtils::numberOfTrailingZeros(20)); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Common/ErrorCorrectionLevelTest.php b/tests/BaconQrCode/Common/ErrorCorrectionLevelTest.php deleted file mode 100644 index 736e995..0000000 --- a/tests/BaconQrCode/Common/ErrorCorrectionLevelTest.php +++ /dev/null @@ -1,40 +0,0 @@ -assertEquals(0x0, ErrorCorrectionLevel::M); - $this->assertEquals(0x1, ErrorCorrectionLevel::L); - $this->assertEquals(0x2, ErrorCorrectionLevel::H); - $this->assertEquals(0x3, ErrorCorrectionLevel::Q); - } - - public function testInvalidErrorCorrectionLevelThrowsException() - { - $this->setExpectedException( - 'BaconQrCode\Exception\UnexpectedValueException', - 'Value not a const in enum BaconQrCode\Common\ErrorCorrectionLevel' - ); - new ErrorCorrectionLevel(4); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Common/FormatInformationTest.php b/tests/BaconQrCode/Common/FormatInformationTest.php deleted file mode 100644 index 5f6ee58..0000000 --- a/tests/BaconQrCode/Common/FormatInformationTest.php +++ /dev/null @@ -1,104 +0,0 @@ -unmaskedTestFormatInfo = $this->maskedTestFormatInfo ^ 0x5412; - } - - - public function testBitsDiffering() - { - $this->assertEquals(0, FormatInformation::numBitsDiffering(1, 1)); - $this->assertEquals(1, FormatInformation::numBitsDiffering(0, 2)); - $this->assertEquals(2, FormatInformation::numBitsDiffering(1, 2)); - $this->assertEquals(32, FormatInformation::numBitsDiffering(-1, 0)); - } - - public function testDecode() - { - $expected = FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo, - $this->maskedTestFormatInfo - ); - - $this->assertNotNull($expected); - $this->assertEquals(7, $expected->getDataMask()); - $this->assertEquals(ErrorCorrectionLevel::Q, $expected->getErrorCorrectionLevel()->get()); - - $this->assertEquals( - $expected, - FormatInformation::decodeFormatInformation( - $this->unmaskedTestFormatInfo, - $this->maskedTestFormatInfo - ) - ); - } - - public function testDecodeWithBitDifference() - { - $expected = FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo, - $this->maskedTestFormatInfo - ); - - $this->assertEquals( - $expected, - FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo ^ 0x1, - $this->maskedTestFormatInfo ^ 0x1 - ) - ); - $this->assertEquals( - $expected, - FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo ^ 0x3, - $this->maskedTestFormatInfo ^ 0x3 - ) - ); - $this->assertEquals( - $expected, - FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo ^ 0x7, - $this->maskedTestFormatInfo ^ 0x7 - ) - ); - $this->assertNull( - FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo ^ 0xf, - $this->maskedTestFormatInfo ^ 0xf - ) - ); - } - - public function testDecodeWithMisRead() - { - $expected = FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo, - $this->maskedTestFormatInfo - ); - - $this->assertEquals( - $expected, - FormatInformation::decodeFormatInformation( - $this->maskedTestFormatInfo ^ 0x3, - $this->maskedTestFormatInfo ^ 0xf - ) - ); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Common/ModeTest.php b/tests/BaconQrCode/Common/ModeTest.php deleted file mode 100644 index 4daab7c..0000000 --- a/tests/BaconQrCode/Common/ModeTest.php +++ /dev/null @@ -1,42 +0,0 @@ -assertEquals(0x0, Mode::TERMINATOR); - $this->assertEquals(0x1, Mode::NUMERIC); - $this->assertEquals(0x2, Mode::ALPHANUMERIC); - $this->assertEquals(0x4, Mode::BYTE); - $this->assertEquals(0x8, Mode::KANJI); - } - - public function testInvalidModeThrowsException() - { - $this->setExpectedException( - 'BaconQrCode\Exception\UnexpectedValueException', - 'Value not a const in enum BaconQrCode\Common\Mode' - ); - new Mode(10); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Common/ReedSolomonCodecTest.php b/tests/BaconQrCode/Common/ReedSolomonCodecTest.php deleted file mode 100644 index 604641a..0000000 --- a/tests/BaconQrCode/Common/ReedSolomonCodecTest.php +++ /dev/null @@ -1,111 +0,0 @@ -encode($block, $parity); - - // Copy parity into test blocks - for ($i = 0; $i < $numRoots; $i++) { - $block[$i + $dataSize] = $parity[$i]; - $tBlock[$i + $dataSize] = $parity[$i]; - } - - // Seed with errors - for ($i = 0; $i < $errors; $i++) { - $errorValue = mt_rand(1, $blockSize); - - do { - $errorLocation = mt_rand(0, $blockSize); - } while ($errorLocations[$errorLocation] !== 0); - - $errorLocations[$errorLocation] = 1; - - if (mt_rand(0, 1)) { - $erasures[] = $errorLocation; - } - - $tBlock[$errorLocation] ^= $errorValue; - } - - $erasures = SplFixedArray::fromArray($erasures, false); - - // Decode the errored block - $foundErrors = $codec->decode($tBlock, $erasures); - - if ($errors > 0 && $foundErrors === null) { - $this->assertEquals($block, $tBlock, 'Decoder failed to correct errors'); - } - - $this->assertEquals($errors, $foundErrors, 'Found errors do not equal expected errors'); - - for ($i = 0; $i < $foundErrors; $i++) { - if ($errorLocations[$erasures[$i]] === 0) { - $this->fail(sprintf('Decoder indicates error in location %d without error', $erasures[$i])); - } - } - - $this->assertEquals($block, $tBlock, 'Decoder did not correct errors'); - } - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Common/VersionTest.php b/tests/BaconQrCode/Common/VersionTest.php deleted file mode 100644 index 8b3fc01..0000000 --- a/tests/BaconQrCode/Common/VersionTest.php +++ /dev/null @@ -1,88 +0,0 @@ -assertNotNull($version); - $this->assertEquals($versionNumber, $version->getVersionNumber()); - $this->assertNotNull($version->getAlignmentPatternCenters()); - - if ($versionNumber > 1) { - $this->assertTrue(count($version->getAlignmentPatternCenters()) > 0); - } - - $this->assertEquals($dimension, $version->getDimensionForVersion()); - $this->assertNotNull($version->getEcBlocksForLevel(new ErrorCorrectionLevel(ErrorCorrectionLevel::H))); - $this->assertNotNull($version->getEcBlocksForLevel(new ErrorCorrectionLevel(ErrorCorrectionLevel::L))); - $this->assertNotNull($version->getEcBlocksForLevel(new ErrorCorrectionLevel(ErrorCorrectionLevel::M))); - $this->assertNotNull($version->getEcBlocksForLevel(new ErrorCorrectionLevel(ErrorCorrectionLevel::Q))); - $this->assertNotNull($version->buildFunctionPattern()); - } - - /** - * @dataProvider versionProvider - * @param integer $versionNumber - * @param integer $dimension - */ - public function testGetProvisionalVersionForDimension($versionNumber, $dimension) - { - $this->assertEquals( - $versionNumber, - Version::getProvisionalVersionForDimension($dimension)->getVersionNumber() - ); - } - - /** - * @dataProvider decodeInformationProvider - * @param integer $expectedVersion - * @param integer $mask - */ - public function testDecodeVersionInformation($expectedVersion, $mask) - { - $version = Version::decodeVersionInformation($mask); - $this->assertNotNull($version); - $this->assertEquals($expectedVersion, $version->getVersionNumber()); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Encoder/EncoderTest.php b/tests/BaconQrCode/Encoder/EncoderTest.php deleted file mode 100644 index 31cdaa4..0000000 --- a/tests/BaconQrCode/Encoder/EncoderTest.php +++ /dev/null @@ -1,468 +0,0 @@ -getMethods(ReflectionMethod::IS_STATIC) as $method) { - $method->setAccessible(true); - $this->methods[$method->getName()] = $method; - } - } - - public function testGetAlphanumericCode() - { - // The first ten code points are numbers. - for ($i = 0; $i < 10; $i++) { - $this->assertEquals($i, $this->methods['getAlphanumericCode']->invoke(null, ord('0') + $i)); - } - - // The next 26 code points are capital alphabet letters. - for ($i = 10; $i < 36; $i++) { - // The first ten code points are numbers - $this->assertEquals($i, $this->methods['getAlphanumericCode']->invoke(null, ord('A') + $i - 10)); - } - - // Others are symbol letters. - $this->assertEquals(36, $this->methods['getAlphanumericCode']->invoke(null, ' ')); - $this->assertEquals(37, $this->methods['getAlphanumericCode']->invoke(null, '$')); - $this->assertEquals(38, $this->methods['getAlphanumericCode']->invoke(null, '%')); - $this->assertEquals(39, $this->methods['getAlphanumericCode']->invoke(null, '*')); - $this->assertEquals(40, $this->methods['getAlphanumericCode']->invoke(null, '+')); - $this->assertEquals(41, $this->methods['getAlphanumericCode']->invoke(null, '-')); - $this->assertEquals(42, $this->methods['getAlphanumericCode']->invoke(null, '.')); - $this->assertEquals(43, $this->methods['getAlphanumericCode']->invoke(null, '/')); - $this->assertEquals(44, $this->methods['getAlphanumericCode']->invoke(null, ':')); - - // Should return -1 for other letters. - $this->assertEquals(-1, $this->methods['getAlphanumericCode']->invoke(null, 'a')); - $this->assertEquals(-1, $this->methods['getAlphanumericCode']->invoke(null, '#')); - $this->assertEquals(-1, $this->methods['getAlphanumericCode']->invoke(null, "\0")); - } - - public function testChooseMode() - { - // Numeric mode - $this->assertSame(Mode::NUMERIC, $this->methods['chooseMode']->invoke(null, '0')->get()); - $this->assertSame(Mode::NUMERIC, $this->methods['chooseMode']->invoke(null, '0123456789')->get()); - - // Alphanumeric mode - $this->assertSame(Mode::ALPHANUMERIC, $this->methods['chooseMode']->invoke(null, 'A')->get()); - $this->assertSame(Mode::ALPHANUMERIC, $this->methods['chooseMode']->invoke(null, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:')->get()); - - // 8-bit byte mode - $this->assertSame(Mode::BYTE, $this->methods['chooseMode']->invoke(null, 'a')->get()); - $this->assertSame(Mode::BYTE, $this->methods['chooseMode']->invoke(null, '#')->get()); - $this->assertSame(Mode::BYTE, $this->methods['chooseMode']->invoke(null, '')->get()); - - // AIUE in Hiragana in SHIFT-JIS - $this->assertSame(Mode::BYTE, $this->methods['chooseMode']->invoke(null, "\x8\xa\x8\xa\x8\xa\x8\xa6")->get()); - - // Nihon in Kanji in SHIFT-JIS - $this->assertSame(Mode::BYTE, $this->methods['chooseMode']->invoke(null, "\x9\xf\x9\x7b")->get()); - - // Sou-Utso-Byou in Kanji in SHIFT-JIS - $this->assertSame(Mode::BYTE, $this->methods['chooseMode']->invoke(null, "\xe\x4\x9\x5\x9\x61")->get()); - } - - public function testEncode() - { - $qrCode = Encoder::encode('ABCDEF', new ErrorCorrectionLevel(ErrorCorrectionLevel::H)); - $expected = "<<\n" - . " mode: ALPHANUMERIC\n" - . " ecLevel: H\n" - . " version: 1\n" - . " maskPattern: 0\n" - . " matrix:\n" - . " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1\n" - . " 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1\n" - . " 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 1 1 0 1\n" - . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" - . " 1 0 1 1 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1\n" - . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1\n" - . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" - . " 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0\n" - . " 0 0 1 0 1 1 1 0 1 1 0 0 1 1 0 0 0 1 0 0 1\n" - . " 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0\n" - . " 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 1 0\n" - . " 1 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 0\n" - . " 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0\n" - . " 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0\n" - . " 1 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1\n" - . " 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1\n" - . " 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 0 1\n" - . " 1 0 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 1 0 1 0\n" - . " 1 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1\n" - . " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 1\n" - . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1\n" - . ">>\n"; - - $this->assertEquals($expected, $qrCode->__toString()); - } - - public function testSimpleUtf8Eci() - { - $qrCode = Encoder::encode('hello', new ErrorCorrectionLevel(ErrorCorrectionLevel::H), 'utf-8'); - $expected = "<<\n" - . " mode: BYTE\n" - . " ecLevel: H\n" - . " version: 1\n" - . " maskPattern: 3\n" - . " matrix:\n" - . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1\n" - . " 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 0 1\n" - . " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1\n" - . " 1 0 1 1 1 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0 1\n" - . " 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1\n" - . " 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1\n" - . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" - . " 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0\n" - . " 0 0 1 1 0 0 1 1 1 1 0 0 0 1 1 0 1 0 0 0 0\n" - . " 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0 1 0 1 1 1 0\n" - . " 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 1 1 1\n" - . " 1 1 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 1 0\n" - . " 0 0 0 0 1 0 1 1 1 1 0 0 0 0 0 1 0 0 1 0 0\n" - . " 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 0 0 0 1\n" - . " 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 0 0 1 0 0\n" - . " 1 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 1\n" - . " 1 0 1 1 1 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 0\n" - . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 0 0 1 1 0 0 0\n" - . " 1 0 1 1 1 0 1 0 1 1 0 0 0 1 0 0 1 0 0 0 0\n" - . " 1 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 1 0 1 1 0\n" - . " 1 1 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 0 0 0 0\n" - . ">>\n"; - - $this->assertEquals($expected, $qrCode->__toString()); - } - - public function testAppendModeInfo() - { - $bits = new BitArray(); - $this->methods['appendModeInfo']->invoke(null, new Mode(Mode::NUMERIC), $bits); - $this->assertEquals(' ...X', $bits->__toString()); - } - - public function testAppendLengthInfo() - { - // 1 letter (1/1), 10 bits. - $bits = new BitArray(); - $this->methods['appendLengthInfo']->invoke( - null, - 1, - Version::getVersionForNumber(1), - new Mode(Mode::NUMERIC), - $bits - ); - $this->assertEquals(' ........ .X', $bits->__toString()); - - // 2 letters (2/1), 11 bits. - $bits = new BitArray(); - $this->methods['appendLengthInfo']->invoke( - null, - 2, - Version::getVersionForNumber(10), - new Mode(Mode::ALPHANUMERIC), - $bits - ); - $this->assertEquals(' ........ .X.', $bits->__toString()); - - // 255 letters (255/1), 16 bits. - $bits = new BitArray(); - $this->methods['appendLengthInfo']->invoke( - null, - 255, - Version::getVersionForNumber(27), - new Mode(Mode::BYTE), - $bits - ); - $this->assertEquals(' ........ XXXXXXXX', $bits->__toString()); - - // 512 letters (1024/2), 12 bits. - $bits = new BitArray(); - $this->methods['appendLengthInfo']->invoke( - null, - 512, - Version::getVersionForNumber(40), - new Mode(Mode::KANJI), - $bits - ); - $this->assertEquals(' ..X..... ....', $bits->__toString()); - } - - public function testAppendBytes() - { - // Should use appendNumericBytes. - // 1 = 01 = 0001 in 4 bits. - $bits = new BitArray(); - $this->methods['appendBytes']->invoke( - null, - '1', - new Mode(Mode::NUMERIC), - $bits, - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->assertEquals(' ...X', $bits->__toString()); - - // Should use appendAlphaNumericBytes. - // A = 10 = 0xa = 001010 in 6 bits. - $bits = new BitArray(); - $this->methods['appendBytes']->invoke( - null, - 'A', - new Mode(Mode::ALPHANUMERIC), - $bits, - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->assertEquals(' ..X.X.', $bits->__toString()); - - // Should use append8BitBytes. - // 0x61, 0x62, 0x63 - $bits = new BitArray(); - $this->methods['appendBytes']->invoke( - null, - 'abc', - new Mode(Mode::BYTE), - $bits, - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->assertEquals(' .XX....X .XX...X. .XX...XX', $bits->__toString()); - - // Should use appendKanjiBytes. - // 0x93, 0x5f - $bits = new BitArray(); - $this->methods['appendBytes']->invoke( - null, - "\x93\x5f", - new Mode(Mode::KANJI), - $bits, - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->assertEquals(' .XX.XX.. XXXXX', $bits->__toString()); - - // Lower letters such as 'a' cannot be encoded in alphanumeric mode. - $this->setExpectedException( - 'BaconQrCode\Exception\WriterException', - 'Invalid alphanumeric code' - ); - $this->methods['appendBytes']->invoke( - null, - "a", - new Mode(Mode::ALPHANUMERIC), - $bits, - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - } - - public function testTerminateBits() - { - $bits = new BitArray(); - $this->methods['terminateBits']->invoke(null, 0, $bits); - $this->assertEquals('', $bits->__toString()); - - $bits = new BitArray(); - $this->methods['terminateBits']->invoke(null, 1, $bits); - $this->assertEquals(' ........', $bits->__toString()); - - $bits = new BitArray(); - $bits->appendBits(0, 3); - $this->methods['terminateBits']->invoke(null, 1, $bits); - $this->assertEquals(' ........', $bits->__toString()); - - $bits = new BitArray(); - $bits->appendBits(0, 5); - $this->methods['terminateBits']->invoke(null, 1, $bits); - $this->assertEquals(' ........', $bits->__toString()); - - $bits = new BitArray(); - $bits->appendBits(0, 8); - $this->methods['terminateBits']->invoke(null, 1, $bits); - $this->assertEquals(' ........', $bits->__toString()); - - $bits = new BitArray(); - $this->methods['terminateBits']->invoke(null, 2, $bits); - $this->assertEquals(' ........ XXX.XX..', $bits->__toString()); - - $bits = new BitArray(); - $bits->appendBits(0, 1); - $this->methods['terminateBits']->invoke(null, 3, $bits); - $this->assertEquals(' ........ XXX.XX.. ...X...X', $bits->__toString()); - } - - public function testGetNumDataBytesAndNumEcBytesForBlockId() - { - // Version 1-H. - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 26, 9, 1, 0); - $this->assertEquals(9, $numDataBytes); - $this->assertEquals(17, $numEcBytes); - - // Version 3-H. 2 blocks. - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 70, 26, 2, 0); - $this->assertEquals(13, $numDataBytes); - $this->assertEquals(22, $numEcBytes); - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 70, 26, 2, 1); - $this->assertEquals(13, $numDataBytes); - $this->assertEquals(22, $numEcBytes); - - // Version 7-H. (4 + 1) blocks. - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 196, 66, 5, 0); - $this->assertEquals(13, $numDataBytes); - $this->assertEquals(26, $numEcBytes); - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 196, 66, 5, 4); - $this->assertEquals(14, $numDataBytes); - $this->assertEquals(26, $numEcBytes); - - // Version 40-H. (20 + 61) blocks. - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 3706, 1276, 81, 0); - $this->assertEquals(15, $numDataBytes); - $this->assertEquals(30, $numEcBytes); - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 3706, 1276, 81, 20); - $this->assertEquals(16, $numDataBytes); - $this->assertEquals(30, $numEcBytes); - list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId']->invoke(null, 3706, 1276, 81, 80); - $this->assertEquals(16, $numDataBytes); - $this->assertEquals(30, $numEcBytes); - } - - public function testInterleaveWithEcBytes() - { - $dataBytes = SplFixedArray::fromArray(array(32, 65, 205, 69, 41, 220, 46, 128, 236), false); - $in = new BitArray(); - - foreach ($dataBytes as $dataByte) { - $in->appendBits($dataByte, 8); - } - - $outBits = $this->methods['interleaveWithEcBytes']->invoke(null, $in, 26, 9, 1); - $expected = SplFixedArray::fromArray(array( - // Data bytes. - 32, 65, 205, 69, 41, 220, 46, 128, 236, - // Error correction bytes. - 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61, - ), false); - - $out = $outBits->toBytes(0, count($expected)); - - $this->assertEquals($expected, $out); - } - - public function testAppendNumericBytes() - { - // 1 = 01 = 0001 in 4 bits. - $bits = new BitArray(); - $this->methods['appendNumericBytes']->invoke(null, '1', $bits); - $this->assertEquals(' ...X', $bits->__toString()); - - // 12 = 0xc = 0001100 in 7 bits. - $bits = new BitArray(); - $this->methods['appendNumericBytes']->invoke(null, '12', $bits); - $this->assertEquals(' ...XX..', $bits->__toString()); - - // 123 = 0x7b = 0001111011 in 10 bits. - $bits = new BitArray(); - $this->methods['appendNumericBytes']->invoke(null, '123', $bits); - $this->assertEquals(' ...XXXX. XX', $bits->__toString()); - - // 1234 = "123" + "4" = 0001111011 + 0100 in 14 bits. - $bits = new BitArray(); - $this->methods['appendNumericBytes']->invoke(null, '1234', $bits); - $this->assertEquals(' ...XXXX. XX.X..', $bits->__toString()); - - // Empty - $bits = new BitArray(); - $this->methods['appendNumericBytes']->invoke(null, '', $bits); - $this->assertEquals('', $bits->__toString()); - } - - public function testAppendAlphanumericBytes() - { - $bits = new BitArray(); - $this->methods['appendAlphanumericBytes']->invoke(null, 'A', $bits); - $this->assertEquals(' ..X.X.', $bits->__toString()); - - $bits = new BitArray(); - $this->methods['appendAlphanumericBytes']->invoke(null, 'AB', $bits); - $this->assertEquals(' ..XXX..X X.X', $bits->__toString()); - - $bits = new BitArray(); - $this->methods['appendAlphanumericBytes']->invoke(null, 'ABC', $bits); - $this->assertEquals(' ..XXX..X X.X..XX. .', $bits->__toString()); - - // Empty - $bits = new BitArray(); - $this->methods['appendAlphanumericBytes']->invoke(null, '', $bits); - $this->assertEquals('', $bits->__toString()); - - // Invalid data - $this->setExpectedException('BaconQrCode\Exception\WriterException', 'Invalid alphanumeric code'); - $bits = new BitArray(); - $this->methods['appendAlphanumericBytes']->invoke(null, 'abc', $bits); - } - - public function testAppend8BitBytes() - { - // 0x61, 0x62, 0x63 - $bits = new BitArray(); - $this->methods['append8BitBytes']->invoke(null, 'abc', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); - $this->assertEquals(' .XX....X .XX...X. .XX...XX', $bits->__toString()); - - // Empty - $bits = new BitArray(); - $this->methods['append8BitBytes']->invoke(null, '', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); - $this->assertEquals('', $bits->__toString()); - } - - public function testAppendKanjiBytes() - { - // Numbers are from page 21 of JISX0510:2004 - $bits = new BitArray(); - $this->methods['appendKanjiBytes']->invoke(null, "\x93\x5f", $bits); - $this->assertEquals(' .XX.XX.. XXXXX', $bits->__toString()); - - $this->methods['appendKanjiBytes']->invoke(null, "\xe4\xaa", $bits); - $this->assertEquals(' .XX.XX.. XXXXXXX. X.X.X.X. X.', $bits->__toString()); - } - - public function testGenerateEcBytes() - { - // Numbers are from http://www.swetake.com/qr/qr3.html and - // http://www.swetake.com/qr/qr9.html - $dataBytes = SplFixedArray::fromArray(array(32, 65, 205, 69, 41, 220, 46, 128, 236), false); - $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); - $expected = SplFixedArray::fromArray(array(42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61), false); - $this->assertEquals($expected, $ecBytes); - - $dataBytes = SplFixedArray::fromArray(array(67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214), false); - $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 18); - $expected = SplFixedArray::fromArray(array(175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187), false); - $this->assertEquals($expected, $ecBytes); - - // High-order zero coefficient case. - $dataBytes = SplFixedArray::fromArray(array(32, 49, 205, 69, 42, 20, 0, 236, 17), false); - $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); - $expected = SplFixedArray::fromArray(array(0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213), false); - $this->assertEquals($expected, $ecBytes); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Encoder/MaskUtilTest.php b/tests/BaconQrCode/Encoder/MaskUtilTest.php deleted file mode 100644 index a5c3865..0000000 --- a/tests/BaconQrCode/Encoder/MaskUtilTest.php +++ /dev/null @@ -1,281 +0,0 @@ -fail('Data mask bit did not match'); - } - } - } - } - - public function testApplyMaskPenaltyRule1() - { - $matrix = new ByteMatrix(4, 1); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 0); - $matrix->set(2, 0, 0); - $matrix->set(3, 0, 0); - - $this->assertEquals(0, MaskUtil::applyMaskPenaltyRule1($matrix)); - - // Horizontal - $matrix = new ByteMatrix(6, 1); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 0); - $matrix->set(2, 0, 0); - $matrix->set(3, 0, 0); - $matrix->set(4, 0, 0); - $matrix->set(5, 0, 1); - $this->assertEquals(3, MaskUtil::applyMaskPenaltyRule1($matrix)); - $matrix->set(5, 0, 0); - $this->assertEquals(4, MaskUtil::applyMaskPenaltyRule1($matrix)); - - // Vertical - $matrix = new ByteMatrix(1, 6); - $matrix->set(0, 0, 0); - $matrix->set(0, 1, 0); - $matrix->set(0, 2, 0); - $matrix->set(0, 3, 0); - $matrix->set(0, 4, 0); - $matrix->set(0, 5, 1); - $this->assertEquals(3, MaskUtil::applyMaskPenaltyRule1($matrix)); - $matrix->set(0, 5, 0); - $this->assertEquals(4, MaskUtil::applyMaskPenaltyRule1($matrix)); - } - - public function testApplyMaskPenaltyRule2() - { - $matrix = new ByteMatrix(1, 1); - $matrix->set(0, 0, 0); - $this->assertEquals(0, MaskUtil::applyMaskPenaltyRule2($matrix)); - - $matrix = new ByteMatrix(2, 2); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 0); - $matrix->set(0, 1, 0); - $matrix->set(1, 1, 1); - $this->assertEquals(0, MaskUtil::applyMaskPenaltyRule2($matrix)); - - $matrix = new ByteMatrix(2, 2); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 0); - $matrix->set(0, 1, 0); - $matrix->set(1, 1, 0); - $this->assertEquals(3, MaskUtil::applyMaskPenaltyRule2($matrix)); - - $matrix = new ByteMatrix(3, 3); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 0); - $matrix->set(2, 0, 0); - $matrix->set(0, 1, 0); - $matrix->set(1, 1, 0); - $matrix->set(2, 1, 0); - $matrix->set(0, 2, 0); - $matrix->set(1, 2, 0); - $matrix->set(2, 2, 0); - $this->assertEquals(3 * 4, MaskUtil::applyMaskPenaltyRule2($matrix)); - } - - public function testApplyMaskPenalty3() - { - // Horizontal 00001011101 - $matrix = new ByteMatrix(11, 1); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 0); - $matrix->set(2, 0, 0); - $matrix->set(3, 0, 0); - $matrix->set(4, 0, 1); - $matrix->set(5, 0, 0); - $matrix->set(6, 0, 1); - $matrix->set(7, 0, 1); - $matrix->set(8, 0, 1); - $matrix->set(9, 0, 0); - $matrix->set(10, 0, 1); - $this->assertEquals(40, MaskUtil::applyMaskPenaltyRule3($matrix)); - - // Horizontal 10111010000 - $matrix = new ByteMatrix(11, 1); - $matrix->set(0, 0, 1); - $matrix->set(1, 0, 0); - $matrix->set(2, 0, 1); - $matrix->set(3, 0, 1); - $matrix->set(4, 0, 1); - $matrix->set(5, 0, 0); - $matrix->set(6, 0, 1); - $matrix->set(7, 0, 0); - $matrix->set(8, 0, 0); - $matrix->set(9, 0, 0); - $matrix->set(10, 0, 0); - $this->assertEquals(40, MaskUtil::applyMaskPenaltyRule3($matrix)); - - // Vertical 00001011101 - $matrix = new ByteMatrix(1, 11); - $matrix->set(0, 0, 0); - $matrix->set(0, 1, 0); - $matrix->set(0, 2, 0); - $matrix->set(0, 3, 0); - $matrix->set(0, 4, 1); - $matrix->set(0, 5, 0); - $matrix->set(0, 6, 1); - $matrix->set(0, 7, 1); - $matrix->set(0, 8, 1); - $matrix->set(0, 9, 0); - $matrix->set(0, 10, 1); - $this->assertEquals(40, MaskUtil::applyMaskPenaltyRule3($matrix)); - - // Vertical 10111010000 - $matrix = new ByteMatrix(1, 11); - $matrix->set(0, 0, 1); - $matrix->set(0, 1, 0); - $matrix->set(0, 2, 1); - $matrix->set(0, 3, 1); - $matrix->set(0, 4, 1); - $matrix->set(0, 5, 0); - $matrix->set(0, 6, 1); - $matrix->set(0, 7, 0); - $matrix->set(0, 8, 0); - $matrix->set(0, 9, 0); - $matrix->set(0, 10, 0); - $this->assertEquals(40, MaskUtil::applyMaskPenaltyRule3($matrix)); - } - - public function testApplyMaskPenaltyRule4() - { - // Dark cell ratio = 0% - $matrix = new ByteMatrix(1, 1); - $matrix->set(0, 0, 0); - $this->assertEquals(100, MaskUtil::applyMaskPenaltyRule4($matrix)); - - // Dark cell ratio = 5% - $matrix = new ByteMatrix(2, 1); - $matrix->set(0, 0, 0); - $matrix->set(0, 0, 1); - $this->assertEquals(0, MaskUtil::applyMaskPenaltyRule4($matrix)); - - // Dark cell ratio = 66.67% - $matrix = new ByteMatrix(6, 1); - $matrix->set(0, 0, 0); - $matrix->set(1, 0, 1); - $matrix->set(2, 0, 1); - $matrix->set(3, 0, 1); - $matrix->set(4, 0, 1); - $matrix->set(5, 0, 0); - $this->assertEquals(30, MaskUtil::applyMaskPenaltyRule4($matrix)); - } -} \ No newline at end of file diff --git a/tests/BaconQrCode/Renderer/Text/HtmlTest.php b/tests/BaconQrCode/Renderer/Text/HtmlTest.php deleted file mode 100644 index 0c69dd2..0000000 --- a/tests/BaconQrCode/Renderer/Text/HtmlTest.php +++ /dev/null @@ -1,99 +0,0 @@ -renderer = new Html(); - $this->writer = new Writer($this->renderer); - } - - public function testBasicRender() - { - $content = 'foobar'; - $expected = - '
' .
-            "                       \n" .
-            " ███████ █████ ███████ \n" .
-            " █     █  █ █  █     █ \n" .
-            " █ ███ █  ██   █ ███ █ \n" .
-            " █ ███ █  ███  █ ███ █ \n" .
-            " █ ███ █   █ █ █ ███ █ \n" .
-            " █     █    ██ █     █ \n" .
-            " ███████ █ █ █ ███████ \n" .
-            "         █████         \n" .
-            " ██ ██ █  ██ █ █     █ \n" .
-            "    ██    ██ █ █ ██    \n" .
-            "  ████████ █  ██ █  ██ \n" .
-            "           ██      █ █ \n" .
-            "  ██  ███  █   █  █  █ \n" .
-            "         █ ███    █ █  \n" .
-            " ███████  ██ ██████    \n" .
-            " █     █   ████   ██   \n" .
-            " █ ███ █ ██ ██ ██ █ ██ \n" .
-            " █ ███ █ ██ ██  █ ██   \n" .
-            " █ ███ █   █   █ ██ ██ \n" .
-            " █     █ ███  ███ ████ \n" .
-            " ███████ ████   ██     \n" .
-            "                       \n" .
-            '
' - ; - - $qrCode = Encoder::encode( - $content, - new ErrorCorrectionLevel(ErrorCorrectionLevel::L), - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->assertEquals($expected, $this->renderer->render($qrCode)); - } - - public function testSetStyle() - { - $content = 'foobar'; - $qrCode = Encoder::encode( - $content, - new ErrorCorrectionLevel(ErrorCorrectionLevel::L), - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->renderer->setStyle('bar'); - $this->assertEquals('bar', $this->renderer->getStyle()); - $this->assertStringMatchesFormat('%astyle="bar"%a', $this->renderer->render($qrCode)); - } - - public function testSetClass() - { - $content = 'foobar'; - $qrCode = Encoder::encode( - $content, - new ErrorCorrectionLevel(ErrorCorrectionLevel::L), - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->renderer->setClass('bar'); - $this->assertEquals('bar', $this->renderer->getClass()); - $this->assertStringMatchesFormat('%aclass="bar"%a', $this->renderer->render($qrCode)); - } -} diff --git a/tests/BaconQrCode/Renderer/Text/TextTest.php b/tests/BaconQrCode/Renderer/Text/TextTest.php deleted file mode 100644 index d94e8e5..0000000 --- a/tests/BaconQrCode/Renderer/Text/TextTest.php +++ /dev/null @@ -1,149 +0,0 @@ -renderer = new Plain(); - $this->writer = new Writer($this->renderer); - } - - public function testBasicRender() - { - $content = 'foobar'; - $expected = - " \n" . - " ███████ █████ ███████ \n" . - " █ █ █ █ █ █ \n" . - " █ ███ █ ██ █ ███ █ \n" . - " █ ███ █ ███ █ ███ █ \n" . - " █ ███ █ █ █ █ ███ █ \n" . - " █ █ ██ █ █ \n" . - " ███████ █ █ █ ███████ \n" . - " █████ \n" . - " ██ ██ █ ██ █ █ █ \n" . - " ██ ██ █ █ ██ \n" . - " ████████ █ ██ █ ██ \n" . - " ██ █ █ \n" . - " ██ ███ █ █ █ █ \n" . - " █ ███ █ █ \n" . - " ███████ ██ ██████ \n" . - " █ █ ████ ██ \n" . - " █ ███ █ ██ ██ ██ █ ██ \n" . - " █ ███ █ ██ ██ █ ██ \n" . - " █ ███ █ █ █ ██ ██ \n" . - " █ █ ███ ███ ████ \n" . - " ███████ ████ ██ \n" . - " \n" - ; - - $qrCode = Encoder::encode( - $content, - new ErrorCorrectionLevel(ErrorCorrectionLevel::L), - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->assertEquals($expected, $this->renderer->render($qrCode)); - } - - public function testBasicRenderNoMargins() - { - $content = 'foobar'; - $expected = - "███████ █████ ███████\n" . - "█ █ █ █ █ █\n" . - "█ ███ █ ██ █ ███ █\n" . - "█ ███ █ ███ █ ███ █\n" . - "█ ███ █ █ █ █ ███ █\n" . - "█ █ ██ █ █\n" . - "███████ █ █ █ ███████\n" . - " █████ \n" . - "██ ██ █ ██ █ █ █\n" . - " ██ ██ █ █ ██ \n" . - " ████████ █ ██ █ ██\n" . - " ██ █ █\n" . - " ██ ███ █ █ █ █\n" . - " █ ███ █ █ \n" . - "███████ ██ ██████ \n" . - "█ █ ████ ██ \n" . - "█ ███ █ ██ ██ ██ █ ██\n" . - "█ ███ █ ██ ██ █ ██ \n" . - "█ ███ █ █ █ ██ ██\n" . - "█ █ ███ ███ ████\n" . - "███████ ████ ██ \n" - ; - - $qrCode = Encoder::encode( - $content, - new ErrorCorrectionLevel(ErrorCorrectionLevel::L), - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->renderer->setMargin(0); - $this->assertEquals(0, $this->renderer->getMargin()); - $this->assertEquals($expected, $this->renderer->render($qrCode)); - } - - public function testBasicRenderCustomChar() - { - $content = 'foobar'; - $expected = - "-----------------------\n" . - "-#######-#####-#######-\n" . - "-#-----#--#-#--#-----#-\n" . - "-#-###-#--##---#-###-#-\n" . - "-#-###-#--###--#-###-#-\n" . - "-#-###-#---#-#-#-###-#-\n" . - "-#-----#----##-#-----#-\n" . - "-#######-#-#-#-#######-\n" . - "---------#####---------\n" . - "-##-##-#--##-#-#-----#-\n" . - "----##----##-#-#-##----\n" . - "--########-#--##-#--##-\n" . - "-----------##------#-#-\n" . - "--##--###--#---#--#--#-\n" . - "---------#-###----#-#--\n" . - "-#######--##-######----\n" . - "-#-----#---####---##---\n" . - "-#-###-#-##-##-##-#-##-\n" . - "-#-###-#-##-##--#-##---\n" . - "-#-###-#---#---#-##-##-\n" . - "-#-----#-###--###-####-\n" . - "-#######-####---##-----\n" . - "-----------------------\n" - ; - - $qrCode = Encoder::encode( - $content, - new ErrorCorrectionLevel(ErrorCorrectionLevel::L), - Encoder::DEFAULT_BYTE_MODE_ECODING - ); - $this->renderer->setFullBlock('#'); - $this->renderer->setEmptyBlock('-'); - $this->assertEquals('#', $this->renderer->getFullBlock()); - $this->assertEquals('-', $this->renderer->getEmptyBlock()); - $this->assertEquals($expected, $this->renderer->render($qrCode)); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 05a4941..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ - - - - . - - - - ../src/ - - -