diff --git a/src/Common/Mode.php b/src/Common/Mode.php new file mode 100644 index 000000000..c53b4f348 --- /dev/null +++ b/src/Common/Mode.php @@ -0,0 +1,88 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Data\{AlphaNum, Byte, Kanji, Number}; +use chillerlan\QRCode\QRCodeException; + +/** + * ISO 18004:2006, 6.4.1, Tables 2 and 3 + */ +class Mode{ + + // ISO/IEC 18004:2000 Table 2 + + /** @var int */ + public const DATA_TERMINATOR = 0b0000; + /** @var int */ + public const DATA_NUMBER = 0b0001; + /** @var int */ + public const DATA_ALPHANUM = 0b0010; + /** @var int */ + public const DATA_BYTE = 0b0100; + /** @var int */ + public const DATA_KANJI = 0b1000; + /** @var int */ + public const DATA_STRCTURED_APPEND = 0b0011; + /** @var int */ + public const DATA_FNC1_FIRST = 0b0101; + /** @var int */ + public const DATA_FNC1_SECOND = 0b1001; + /** @var int */ + public const DATA_ECI = 0b0111; + + /** + * mode length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator + */ + public const LENGTH_BITS = [ + self::DATA_NUMBER => [10, 12, 14], + self::DATA_ALPHANUM => [9, 11, 13], + self::DATA_BYTE => [8, 16, 16], + self::DATA_KANJI => [8, 10, 12], + ]; + + /** + * Map of data mode => interface (detection order) + * + * @var string[] + */ + public const DATA_INTERFACES = [ + Mode::DATA_NUMBER => Number::class, + Mode::DATA_ALPHANUM => AlphaNum::class, + Mode::DATA_KANJI => Kanji::class, + Mode::DATA_BYTE => Byte::class, + ]; + + /** + * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function getLengthBitsForVersion(int $mode, int $version):int{ + + if(!isset(self::LENGTH_BITS[$mode])){ + throw new QRCodeException('invalid mode given'); + } + + foreach([9, 26, 40] as $key => $breakpoint){ + if($version <= $breakpoint){ + return self::LENGTH_BITS[$mode][$key]; + } + } + + throw new QRCodeException(sprintf('invalid version number: %d', $version)); + } + +} diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index a78d8fec3..8252b17d2 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Helpers\BitBuffer; -use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\Common\Mode; use function ceil, ord, sprintf, str_split; @@ -39,7 +39,7 @@ final class AlphaNum extends QRDataModeAbstract{ '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44, ]; - protected array $lengthBits = [9, 11, 13]; + protected int $datamode = Mode::DATA_ALPHANUM; /** * @inheritdoc @@ -69,8 +69,8 @@ final class AlphaNum extends QRDataModeAbstract{ $len = $this->getCharCount(); $bitBuffer - ->put(QRCode::DATA_ALPHANUM, 4) - ->put($len, $this->getLengthBitsForVersion($version)) + ->put($this->datamode, 4) + ->put($len, Mode::getLengthBitsForVersion($this->datamode, $version)) ; // encode 2 characters in 11 bits diff --git a/src/Data/Byte.php b/src/Data/Byte.php index 738ec7d32..0a57590cc 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Helpers\BitBuffer; -use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\Common\Mode; use function ord; @@ -25,7 +25,7 @@ use function ord; */ final class Byte extends QRDataModeAbstract{ - protected array $lengthBits = [8, 16, 16]; + protected int $datamode = Mode::DATA_BYTE; /** * @inheritdoc @@ -48,8 +48,8 @@ final class Byte extends QRDataModeAbstract{ $len = $this->getCharCount(); $bitBuffer - ->put(QRCode::DATA_BYTE, 4) - ->put($len, $this->getLengthBitsForVersion($version)) + ->put($this->datamode, 4) + ->put($len, Mode::getLengthBitsForVersion($this->datamode, $version)) ; $i = 0; diff --git a/src/Data/ECI.php b/src/Data/ECI.php index d28555235..54a6867ec 100644 --- a/src/Data/ECI.php +++ b/src/Data/ECI.php @@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Helpers\BitBuffer; -use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\Common\Mode; /** * Adds an ECI Designator @@ -59,6 +59,8 @@ class ECI extends QRDataModeAbstract{ */ protected int $encoding; + protected int $datamode = Mode::DATA_ECI; + /** * @inheritDoc */ @@ -87,7 +89,7 @@ class ECI extends QRDataModeAbstract{ */ public function write(BitBuffer $bitBuffer, int $version):void{ $bitBuffer - ->put(QRCode::DATA_ECI, 4) + ->put($this->datamode, 4) ->put($this->encoding, 8) ; } diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index 676fe2a93..8cb87c4f6 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Helpers\BitBuffer; -use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\Common\Mode; use function mb_convert_encoding, mb_detect_encoding, mb_strlen, ord, sprintf, strlen; @@ -25,7 +25,7 @@ use function mb_convert_encoding, mb_detect_encoding, mb_strlen, ord, sprintf, s */ final class Kanji extends QRDataModeAbstract{ - protected array $lengthBits = [8, 10, 12]; + protected int $datamode = Mode::DATA_KANJI; public function __construct(string $data){ parent::__construct($data); @@ -76,8 +76,8 @@ final class Kanji extends QRDataModeAbstract{ public function write(BitBuffer $bitBuffer, int $version):void{ $bitBuffer - ->put(QRCode::DATA_KANJI, 4) - ->put($this->getCharCount(), $this->getLengthBitsForVersion($version)) + ->put($this->datamode, 4) + ->put($this->getCharCount(), Mode::getLengthBitsForVersion($this->datamode, $version)) ; $len = strlen($this->data); diff --git a/src/Data/Number.php b/src/Data/Number.php index 0fe616ff9..cbcd78cd7 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -13,7 +13,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Helpers\BitBuffer; -use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\Common\Mode; use function ceil, ord, sprintf, str_split, substr; @@ -32,7 +32,7 @@ final class Number extends QRDataModeAbstract{ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, ]; - protected array $lengthBits = [10, 12, 14]; + protected int $datamode = Mode::DATA_NUMBER; /** * @inheritdoc @@ -62,8 +62,8 @@ final class Number extends QRDataModeAbstract{ $len = $this->getCharCount(); $bitBuffer - ->put(QRCode::DATA_NUMBER, 4) - ->put($len, $this->getLengthBitsForVersion($version)) + ->put($this->datamode, 4) + ->put($len, Mode::getLengthBitsForVersion($this->datamode, $version)) ; $i = 0; diff --git a/src/Data/QRData.php b/src/Data/QRData.php index c4fa5a2f5..3e9726fe0 100644 --- a/src/Data/QRData.php +++ b/src/Data/QRData.php @@ -14,6 +14,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial}; +use chillerlan\QRCode\Common\Mode; use chillerlan\Settings\SettingsContainerInterface; use function array_column, array_combine, array_fill, array_keys, array_merge, count, max, range, sprintf; @@ -221,7 +222,7 @@ class QRData{ foreach($this->dataSegments as $segment){ // data length in bits of the current segment +4 bits for each mode descriptor - $length += ($segment->getLengthInBits() + $segment->getLengthBits(0) + 4); + $length += ($segment->getLengthInBits() + Mode::LENGTH_BITS[$segment->getDataMode()][0] + 4); if(!$segment instanceof ECI){ // mode length bits margin to the next breakpoint diff --git a/src/Data/QRDataModeAbstract.php b/src/Data/QRDataModeAbstract.php index dadfb52a2..062707d7d 100644 --- a/src/Data/QRDataModeAbstract.php +++ b/src/Data/QRDataModeAbstract.php @@ -12,18 +12,14 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\Helpers\BitBuffer; - /** */ abstract class QRDataModeAbstract implements QRDataModeInterface{ /** - * mode length bits for the version breakpoints 1-9, 10-26 and 27-40 - * - * ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator + * the current data mode: Num, Alphanum, Kanji, Byte */ - protected array $lengthBits = [0, 0, 0]; + protected int $datamode; /** * The data to write @@ -34,7 +30,7 @@ abstract class QRDataModeAbstract implements QRDataModeInterface{ * QRDataModeAbstract constructor. */ public function __construct(string $data){ - $this->data = $data; + $this->data = $data; } /** @@ -47,25 +43,8 @@ abstract class QRDataModeAbstract implements QRDataModeInterface{ /** * @inheritDoc */ - public function getLengthBits(int $k):int{ - return $this->lengthBits[$k] ?? 0; - } - - /** - * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 - * - * @throws \chillerlan\QRCode\Data\QRCodeDataException - * @codeCoverageIgnore - */ - protected function getLengthBitsForVersion(int $version):int{ - - foreach([9, 26, 40] as $key => $breakpoint){ - if($version <= $breakpoint){ - return $this->getLengthBits($key); - } - } - - throw new QRCodeDataException(sprintf('invalid version number: %d', $version)); + public function getDataMode():int{ + return $this->datamode; } } diff --git a/src/Data/QRDataModeInterface.php b/src/Data/QRDataModeInterface.php index e24886ca4..91839a442 100644 --- a/src/Data/QRDataModeInterface.php +++ b/src/Data/QRDataModeInterface.php @@ -21,9 +21,9 @@ use chillerlan\QRCode\Helpers\BitBuffer; interface QRDataModeInterface{ /** - * returns the length bits for the given breakpoint [0,1,2] + * returns the current data mode constant */ - public function getLengthBits(int $k):int; + public function getDataMode():int; /** * retruns the length in bits of the data string diff --git a/src/QRCode.php b/src/QRCode.php index 5750c572c..5a29d5f22 100755 --- a/src/QRCode.php +++ b/src/QRCode.php @@ -13,6 +13,7 @@ namespace chillerlan\QRCode; use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, MaskPatternTester, Number, QRData, QRCodeDataException, QRMatrix}; +use chillerlan\QRCode\Common\Mode; use chillerlan\QRCode\Output\{ QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString }; @@ -36,19 +37,6 @@ class QRCode{ /** @var int */ public const MASK_PATTERN_AUTO = -1; - // ISO/IEC 18004:2000 Table 2 - - /** @var int */ - public const DATA_NUMBER = 0b0001; - /** @var int */ - public const DATA_ALPHANUM = 0b0010; - /** @var int */ - public const DATA_BYTE = 0b0100; - /** @var int */ - public const DATA_KANJI = 0b1000; - /** @var int */ - public const DATA_ECI = 0b0111; - // ISO/IEC 18004:2000 Tables 12, 25 /** @var int */ @@ -124,18 +112,6 @@ class QRCode{ ], ]; - /** - * Map of data mode => interface (detection order) - * - * @var string[] - */ - protected const DATA_INTERFACES = [ - self::DATA_NUMBER => Number::class, - self::DATA_ALPHANUM => AlphaNum::class, - self::DATA_KANJI => Kanji::class, - self::DATA_BYTE => Byte::class, - ]; - /** * A collection of one or more data segments of [classname, data] to write * @@ -175,7 +151,7 @@ class QRCode{ if($data !== null){ /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */ - foreach($this::DATA_INTERFACES as $dataInterface){ + foreach(Mode::DATA_INTERFACES as $dataInterface){ if($dataInterface::validateString($data)){ $this->addSegment($data, $dataInterface); diff --git a/tests/Helpers/BitBufferTest.php b/tests/Helpers/BitBufferTest.php index e9479a5c6..886ffbb1c 100644 --- a/tests/Helpers/BitBufferTest.php +++ b/tests/Helpers/BitBufferTest.php @@ -12,8 +12,8 @@ namespace chillerlan\QRCodeTest\Helpers; -use chillerlan\QRCode\QRCode; use chillerlan\QRCode\Helpers\BitBuffer; +use chillerlan\QRCode\Common\Mode; use PHPUnit\Framework\TestCase; /** @@ -29,10 +29,10 @@ final class BitBufferTest extends TestCase{ public function bitProvider():array{ return [ - 'number' => [QRCode::DATA_NUMBER, 16], - 'alphanum' => [QRCode::DATA_ALPHANUM, 32], - 'byte' => [QRCode::DATA_BYTE, 64], - 'kanji' => [QRCode::DATA_KANJI, 128], + 'number' => [Mode::DATA_NUMBER, 16], + 'alphanum' => [Mode::DATA_ALPHANUM, 32], + 'byte' => [Mode::DATA_BYTE, 64], + 'kanji' => [Mode::DATA_KANJI, 128], ]; }