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/
-
-
-