init web ems all

This commit is contained in:
agtuser
2024-09-27 17:13:36 +08:00
parent 81c97acbe9
commit 5cc56f8078
4263 changed files with 798779 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
use BaconQrCode\Exception;
use ReflectionClass;
/**
* A general enum implementation until we got SplEnum.
*/
abstract class AbstractEnum
{
/**
* Default value.
*/
const __default = null;
/**
* Current value.
*
* @var mixed
*/
protected $value;
/**
* Cache of constants.
*
* @var array
*/
protected $constants;
/**
* Whether to handle values strict or not.
*
* @var boolean
*/
protected $strict;
/**
* Creates a new enum.
*
* @param mixed $initialValue
* @param boolean $strict
*/
public function __construct($initialValue = null, $strict = false)
{
$this->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());
}
}

View File

@@ -0,0 +1,435 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
use SplFixedArray;
/**
* A simple, fast array of bits.
*/
class BitArray
{
/**
* Bits represented as an array of integers.
*
* @var SplFixedArray
*/
protected $bits;
/**
* Size of the bit array in bits.
*
* @var integer
*/
protected $size;
/**
* Creates a new bit array with a given size.
*
* @param integer $size
*/
public function __construct($size = 0)
{
$this->size = $size;
$this->bits = new SplFixedArray(($this->size + 31) >> 3);
}
/**
* Gets the size in bits.
*
* @return integer
*/
public function getSize()
{
return $this->size;
}
/**
* Gets the size in bytes.
*
* @return integer
*/
public function getSizeInBytes()
{
return ($this->size + 7) >> 3;
}
/**
* Ensures that the array has a minimum capacity.
*
* @param integer $size
* @return void
*/
public function ensureCapacity($size)
{
if ($size > count($this->bits) << 5) {
$this->bits->setSize(($size + 31) >> 5);
}
}
/**
* Gets a specific bit.
*
* @param integer $i
* @return boolean
*/
public function get($i)
{
return ($this->bits[$i >> 5] & (1 << ($i & 0x1f))) !== 0;
}
/**
* Sets a specific bit.
*
* @param integer $i
* @return void
*/
public function set($i)
{
$this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
}
/**
* Flips a specific bit.
*
* @param integer $i
* @return void
*/
public function flip($i)
{
$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)
{
if ($from >= $this->size) {
return $this->size;
}
$bitsOffset = $from >> 5;
$currentBits = $this->bits[$bitsOffset];
$bitsLength = count($this->bits);
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
while ($currentBits === 0) {
if (++$bitsOffset === $bitsLength) {
return $this->size;
}
$currentBits = $this->bits[$bitsOffset];
}
$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)
{
if ($from >= $this->size) {
return $this->size;
}
$bitsOffset = $from >> 5;
$currentBits = ~$this->bits[$bitsOffset];
$bitsLength = count($this->bits);
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
while ($currentBits === 0) {
if (++$bitsOffset === $bitsLength) {
return $this->size;
}
$currentBits = ~$this->bits[$bitsOffset];
}
$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)
{
$this->bits[$i >> 5] = $newBits;
}
/**
* Sets a range of bits.
*
* @param integer $start
* @param integer $end
* @return void
* @throws Exception\InvalidArgumentException
*/
public function setRange($start, $end)
{
if ($end < $start) {
throw new Exception\InvalidArgumentException('End must be greater or equal to start');
}
if ($end === $start) {
return;
}
$end--;
$firstInt = $start >> 5;
$lastInt = $end >> 5;
for ($i = $firstInt; $i <= $lastInt; $i++) {
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
if ($firstBit === 0 && $lastBit === 31) {
$mask = 0x7fffffff;
} else {
$mask = 0;
for ($j = $firstBit; $j < $lastBit; $j++) {
$mask |= 1 << $j;
}
}
$this->bits[$i] = $this->bits[$i] | $mask;
}
}
/**
* Clears the bit array, unsetting every bit.
*
* @return void
*/
public function clear()
{
$bitsLength = count($this->bits);
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
*/
public function isRange($start, $end, $value)
{
if ($end < $start) {
throw new Exception\InvalidArgumentException('End must be greater or equal to start');
}
if ($end === $start) {
return;
}
$end--;
$firstInt = $start >> 5;
$lastInt = $end >> 5;
for ($i = $firstInt; $i <= $lastInt; $i++) {
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
if ($firstBit === 0 && $lastBit === 31) {
$mask = 0x7fffffff;
} else {
$mask = 0;
for ($j = $firstBit; $j <= $lastBit; $j++) {
$mask |= 1 << $j;
}
}
if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
return false;
}
}
return true;
}
/**
* Appends a bit to the array.
*
* @param boolean $bit
* @return void
*/
public function appendBit($bit)
{
$this->ensureCapacity($this->size + 1);
if ($bit) {
$this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
}
$this->size++;
}
/**
* Appends a number of bits (up to 32) to the array.
*
* @param integer $value
* @param integer $numBits
* @return void
* @throws Exception\InvalidArgumentException
*/
public function appendBits($value, $numBits)
{
if ($numBits < 0 || $numBits > 32) {
throw new Exception\InvalidArgumentException('Num bits must be between 0 and 32');
}
$this->ensureCapacity($this->size + $numBits);
for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
$this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
}
}
/**
* Appends another bit array to this array.
*
* @param BitArray $other
* @return void
*/
public function appendBitArray(self $other)
{
$otherSize = $other->getSize();
$this->ensureCapacity($this->size + $other->getSize());
for ($i = 0; $i < $otherSize; $i++) {
$this->appendBit($other->get($i));
}
}
/**
* Makes an exclusive-or comparision on the current bit array.
*
* @param BitArray $other
* @return void
* @throws Exception\InvalidArgumentException
*/
public function xorBits(self $other)
{
$bitsLength = count($this->bits);
$otherBits = $other->getBitArray();
if ($bitsLength !== count($otherBits)) {
throw new Exception\InvalidArgumentException('Sizes don\'t match');
}
for ($i = 0; $i < $bitsLength; $i++) {
$this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
}
}
/**
* Converts the bit array to a byte array.
*
* @param integer $bitOffset
* @param integer $numBytes
* @return SplFixedArray
*/
public function toBytes($bitOffset, $numBytes)
{
$bytes = new SplFixedArray($numBytes);
for ($i = 0; $i < $numBytes; $i++) {
$byte = 0;
for ($j = 0; $j < 8; $j++) {
if ($this->get($bitOffset)) {
$byte |= 1 << (7 - $j);
}
$bitOffset++;
}
$bytes[$i] = $byte;
}
return $bytes;
}
/**
* Gets the internal bit array.
*
* @return SplFixedArray
*/
public function getBitArray()
{
return $this->bits;
}
/**
* Reverses the array.
*
* @return void
*/
public function reverse()
{
$newBits = new SplFixedArray(count($this->bits));
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;
}
/**
* Returns a string representation of the bit array.
*
* @return string
*/
public function __toString()
{
$result = '';
for ($i = 0; $i < $this->size; $i++) {
if (($i & 0x07) === 0) {
$result .= ' ';
}
$result .= $this->get($i) ? 'X' : '.';
}
return $result;
}
}

View File

@@ -0,0 +1,350 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
use SplFixedArray;
/**
* Bit matrix.
*
* Represents a 2D matrix of bits. In function arguments below, and throughout
* the common module, x is the column position, and y is the row position. The
* ordering is always x, y. The origin is at the top-left.
*/
class BitMatrix
{
/**
* Width of the bit matrix.
*
* @var integer
*/
protected $width;
/**
* Height of the bit matrix.
*
* @var integer
*/
protected $height;
/**
* Size in bits of each individual row.
*
* @var integer
*/
protected $rowSize;
/**
* Bits representation.
*
* @var SplFixedArray
*/
protected $bits;
/**
* Creates a new bit matrix with given dimensions.
*
* @param integer $width
* @param integer|null $height
* @throws Exception\InvalidArgumentException
*/
public function __construct($width, $height = null)
{
if ($height === null) {
$height = $width;
}
if ($width < 1 || $height < 1) {
throw new Exception\InvalidArgumentException('Both dimensions must be greater than zero');
}
$this->width = $width;
$this->height = $height;
$this->rowSize = ($width + 31) >> 5;
$this->bits = new SplFixedArray($this->rowSize * $height);
}
/**
* Gets the requested bit, where true means black.
*
* @param integer $x
* @param integer $y
* @return boolean
*/
public function get($x, $y)
{
$offset = $y * $this->rowSize + ($x >> 5);
return (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1) !== 0;
}
/**
* Sets the given bit to true.
*
* @param integer $x
* @param integer $y
* @return void
*/
public function set($x, $y)
{
$offset = $y * $this->rowSize + ($x >> 5);
$this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
}
/**
* Flips the given bit.
*
* @param integer $x
* @param integer $y
* @return void
*/
public function flip($x, $y)
{
$offset = $y * $this->rowSize + ($x >> 5);
$this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
}
/**
* Clears all bits (set to false).
*
* @return void
*/
public function clear()
{
$max = count($this->bits);
for ($i = 0; $i < $max; $i++) {
$this->bits[$i] = 0;
}
}
/**
* Sets a square region of the bit matrix to true.
*
* @param integer $left
* @param integer $top
* @param integer $width
* @param integer $height
* @return void
*/
public function setRegion($left, $top, $width, $height)
{
if ($top < 0 || $left < 0) {
throw new Exception\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');
}
$right = $left + $width;
$bottom = $top + $height;
if ($bottom > $this->height || $right > $this->width) {
throw new Exception\InvalidArgumentException('The region must fit inside the matrix');
}
for ($y = $top; $y < $bottom; $y++) {
$offset = $y * $this->rowSize;
for ($x = $left; $x < $right; $x++) {
$index = $offset + ($x >> 5);
$this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
}
}
}
/**
* 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)
{
if ($row === null || $row->getSize() < $this->width) {
$row = new BitArray($this->width);
}
$offset = $y * $this->rowSize;
for ($x = 0; $x < $this->rowSize; $x++) {
$row->setBulk($x << 5, $this->bits[$offset + $x]);
}
return $row;
}
/**
* Sets a row of data from a BitArray.
*
* @param integer $y
* @param BitArray $row
* @return void
*/
public function setRow($y, BitArray $row)
{
$bits = $row->getBitArray();
for ($i = 0; $i < $this->rowSize; $i++) {
$this->bits[$y * $this->rowSize + $i] = $bits[$i];
}
}
/**
* This is useful in detecting the enclosing rectangle of a 'pure' barcode.
*
* @return SplFixedArray
*/
public function getEnclosingRectangle()
{
$left = $this->width;
$top = $this->height;
$right = -1;
$bottom = -1;
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 ($y < $top) {
$top = $y;
}
if ($y > $bottom) {
$bottom = $y;
}
if ($x32 * 32 < $left) {
$bit = 0;
while (($bits << (31 - $bit)) === 0) {
$bit++;
}
if (($x32 * 32 + $bit) < $left) {
$left = $x32 * 32 + $bit;
}
}
}
if ($x32 * 32 + 31 > $right) {
$bit = 31;
while (BitUtils::unsignedRightShift($bits, $bit) === 0) {
$bit--;
}
if (($x32 * 32 + $bit) > $right) {
$right = $x32 * 32 + $bit;
}
}
}
}
$width = $right - $left;
$height = $bottom - $top;
if ($width < 0 || $height < 0) {
return null;
}
return SplFixedArray::fromArray(array($left, $top, $width, $height), false);
}
/**
* Gets the most top left set bit.
*
* This is useful in detecting a corner of a 'pure' barcode.
*
* @return SplFixedArray
*/
public function getTopLeftOnBit()
{
$bitsOffset = 0;
while ($bitsOffset < count($this->bits) && $this->bits[$bitsOffset] === 0) {
$bitsOffset++;
}
if ($bitsOffset === count($this->bits)) {
return null;
}
$x = intval($bitsOffset / $this->rowSize);
$y = ($bitsOffset % $this->rowSize) << 5;
$bits = $this->bits[$bitsOffset];
$bit = 0;
while (($bits << (31 - $bit)) === 0) {
$bit++;
}
$x += $bit;
return SplFixedArray::fromArray(array($x, $y), false);
}
/**
* Gets the most bottom right set bit.
*
* This is useful in detecting a corner of a 'pure' barcode.
*
* @return SplFixedArray
*/
public function getBottomRightOnBit()
{
$bitsOffset = count($this->bits) - 1;
while ($bitsOffset >= 0 && $this->bits[$bitsOffset] === 0) {
$bitsOffset--;
}
if ($bitsOffset < 0) {
return null;
}
$x = intval($bitsOffset / $this->rowSize);
$y = ($bitsOffset % $this->rowSize) << 5;
$bits = $this->bits[$bitsOffset];
$bit = 0;
while (BitUtils::unsignedRightShift($bits, $bit) === 0) {
$bit--;
}
$x += $bit;
return SplFixedArray::fromArray(array($x, $y), false);
}
/**
* Gets the width of the matrix,
*
* @return integer
*/
public function getWidth()
{
return $this->width;
}
/**
* Gets the height of the matrix.
*
* @return integer
*/
public function getHeight()
{
return $this->height;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
/**
* General bit utilities.
*
* All utility methods are based on 32-bit integers and also work on 64-bit
* systems.
*/
class BitUtils
{
/**
* Performs an unsigned right shift.
*
* This is the same as the unsigned right shift operator ">>>" in other
* languages.
*
* @param integer $a
* @param integer $b
* @return integer
*/
public static function unsignedRightShift($a, $b)
{
return (
$a >= 0
? $a >> $b
: (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
);
}
/**
* Gets the number of trailing zeros.
*
* @param integer $i
* @return integer
*/
public static function numberOfTrailingZeros($i)
{
$lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
return $lastPos === false ? 32 : 31 - $lastPos;
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
/**
* Encapsulates a Character Set ECI, according to "Extended Channel
* Interpretations" 5.3.1.1 of ISO 18004.
*/
class CharacterSetEci extends AbstractEnum
{
/**#@+
* Character set constants.
*/
const CP437 = 0;
const ISO8859_1 = 1;
const ISO8859_2 = 4;
const ISO8859_3 = 5;
const ISO8859_4 = 6;
const ISO8859_5 = 7;
const ISO8859_6 = 8;
const ISO8859_7 = 9;
const ISO8859_8 = 10;
const ISO8859_9 = 11;
const ISO8859_10 = 12;
const ISO8859_11 = 13;
const ISO8859_12 = 14;
const ISO8859_13 = 15;
const ISO8859_14 = 16;
const ISO8859_15 = 17;
const ISO8859_16 = 18;
const SJIS = 20;
const CP1250 = 21;
const CP1251 = 22;
const CP1252 = 23;
const CP1256 = 24;
const UNICODE_BIG_UNMARKED = 25;
const UTF8 = 26;
const ASCII = 27;
const BIG5 = 28;
const GB18030 = 29;
const EUC_KR = 30;
/**#@-*/
/**
* Map between character names and their ECI values.
*
* @var array
*/
protected static $nameToEci = array(
'ISO-8859-1' => 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;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
/**
* Encapsualtes the parameters for one error-correction block in one symbol
* version. This includes the number of data codewords, and the number of times
* a block with these parameters is used consecutively in the QR code version's
* format.
*/
class EcBlock
{
/**
* How many times the block is used.
*
* @var integer
*/
protected $count;
/**
* Number of data codewords.
*
* @var integer
*/
protected $dataCodewords;
/**
* Creates a new EC block.
*
* @param integer $count
* @param integer $dataCodewords
*/
public function __construct($count, $dataCodewords)
{
$this->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;
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
use SplFixedArray;
/**
* Encapsulates a set of error-correction blocks in one symbol version. Most
* versions will use blocks of differing sizes within one version, so, this
* encapsulates the parameters for each set of blocks. It also holds the number
* of error-correction codewords per block since it will be the same across all
* blocks within one version.
*/
class EcBlocks
{
/**
* Number of EC codewords per block.
*
* @var integer
*/
protected $ecCodewordsPerBlock;
/**
* List of EC blocks.
*
* @var SplFixedArray
*/
protected $ecBlocks;
/**
* Creates a new EC blocks instance.
*
* @param integer $ecCodewordsPerBlock
* @param EcBlock $ecb1
* @param EcBlock|null $ecb2
*/
public function __construct($ecCodewordsPerBlock, EcBlock $ecb1, EcBlock $ecb2 = null)
{
$this->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;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
/**
* Enum representing the four error correction levels.
*/
class ErrorCorrectionLevel extends AbstractEnum
{
/**
* Level L, ~7% correction.
*/
const L = 0x1;
/**
* Level M, ~15% correction.
*/
const M = 0x0;
/**
* Level Q, ~25% correction.
*/
const Q = 0x3;
/**
* Level H, ~30% correction.
*/
const H = 0x2;
/**
* Gets the ordinal of this enumeration constant.
*
* @return integer
*/
public function getOrdinal()
{
switch ($this->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;
}
}
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
/**
* Encapsulates a QR Code's format information, including the data mask used and
* error correction level.
*/
class FormatInformation
{
/**
* Mask for format information.
*/
const FORMAT_INFO_MASK_QR = 0x5412;
/**
* Lookup table for decoding format information.
*
* See ISO 18004:2006, Annex C, Table C.1
*
* @var array
*/
protected static $formatInfoDecodeLookup = array(
array(0x5412, 0x00),
array(0x5125, 0x01),
array(0x5e7c, 0x02),
array(0x5b4b, 0x03),
array(0x45f9, 0x04),
array(0x40ce, 0x05),
array(0x4f97, 0x06),
array(0x4aa0, 0x07),
array(0x77c4, 0x08),
array(0x72f3, 0x09),
array(0x7daa, 0x0a),
array(0x789d, 0x0b),
array(0x662f, 0x0c),
array(0x6318, 0x0d),
array(0x6c41, 0x0e),
array(0x6976, 0x0f),
array(0x1689, 0x10),
array(0x13be, 0x11),
array(0x1ce7, 0x12),
array(0x19d0, 0x13),
array(0x0762, 0x14),
array(0x0255, 0x15),
array(0x0d0c, 0x16),
array(0x083b, 0x17),
array(0x355f, 0x18),
array(0x3068, 0x19),
array(0x3f31, 0x1a),
array(0x3a06, 0x1b),
array(0x24b4, 0x1c),
array(0x2183, 0x1d),
array(0x2eda, 0x1e),
array(0x2bed, 0x1f),
);
/**
* Offset i holds the number of 1 bits in the binary representation of i.
*
* @var array
*/
protected static $bitsSetInHalfByte = array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
/**
* Error correction level.
*
* @var ErrorCorrectionLevel
*/
protected $ecLevel;
/**
* Data mask.
*
* @var integer
*/
protected $dataMask;
/**
* Creates a new format information instance.
*
* @param integer $formatInfo
*/
protected function __construct($formatInfo)
{
$this->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()
);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
/**
* Enum representing various modes in which data can be encoded to bits.
*/
class Mode extends AbstractEnum
{
/**#@+
* Mode constants.
*/
const TERMINATOR = 0x0;
const NUMERIC = 0x1;
const ALPHANUMERIC = 0x2;
const STRUCTURED_APPEND = 0x3;
const BYTE = 0x4;
const ECI = 0x7;
const KANJI = 0x8;
const FNC1_FIRST_POSITION = 0x5;
const FNC1_SECOND_POSITION = 0x9;
const HANZI = 0xd;
/**#@-*/
/**
* Character count bits for each version.
*
* @var array
*/
protected static $characterCountBitsForVersions = array(
self::TERMINATOR => 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];
}
}

View File

@@ -0,0 +1,476 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
use BaconQrCode\Exception;
use SplFixedArray;
/**
* Reed-Solomon codec for 8-bit characters.
*
* Based on libfec by Phil Karn, KA9Q.
*/
class ReedSolomonCodec
{
/**
* Symbol size in bits.
*
* @var integer
*/
protected $symbolSize;
/**
* Block size in symbols.
*
* @var integer
*/
protected $blockSize;
/**
* First root of RS code generator polynomial, index form.
*
* @var integer
*/
protected $firstRoot;
/**
* Primitive element to generate polynomial roots, index form.
*
* @var integer
*/
protected $primitive;
/**
* Prim-th root of 1, index form.
*
* @var integer
*/
protected $iPrimitive;
/**
* RS code generator polynomial degree (number of roots).
*
* @var integer
*/
protected $numRoots;
/**
* Padding bytes at front of shortened block.
*
* @var integer
*/
protected $padding;
/**
* Log lookup table.
*
* @var SplFixedArray
*/
protected $alphaTo;
/**
* Anti-Log lookup table.
*
* @var SplFixedArray
*/
protected $indexOf;
/**
* Generator polynomial.
*
* @var SplFixedArray
*/
protected $generatorPoly;
/**
* Creates a new reed solomon instance.
*
* @param integer $symbolSize
* @param integer $gfPoly
* @param integer $firstRoot
* @param integer $primitive
* @param integer $numRoots
* @param integer $padding
* @throws Exception\InvalidArgumentException
* @throws Exception\RuntimeException
*/
public function __construct($symbolSize, $gfPoly, $firstRoot, $primitive, $numRoots, $padding)
{
if ($symbolSize < 0 || $symbolSize > 8) {
throw new Exception\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));
}
if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
throw new Exception\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));
}
$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);
// Generate galous field lookup table
$this->indexOf[0] = $this->blockSize;
$this->alphaTo[$this->blockSize] = 0;
$sr = 1;
for ($i = 0; $i < $this->blockSize; $i++) {
$this->indexOf[$sr] = $i;
$this->alphaTo[$i] = $sr;
$sr <<= 1;
if ($sr & (1 << $symbolSize)) {
$sr ^= $gfPoly;
}
$sr &= $this->blockSize;
}
if ($sr !== 1) {
throw new Exception\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;
// Find prim-th root of 1, used in decoding
for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize);
$this->iPrimitive = intval($iPrimitive / $primitive);
$this->generatorPoly[0] = 1;
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)];
} else {
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
}
}
$this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
}
// Convert generator poly to index form for quicker encoding
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)
{
for ($i = 0; $i < $this->numRoots; $i++) {
$parity[$i] = 0;
}
$iterations = $this->blockSize - $this->numRoots - $this->padding;
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 = 0; $j < $this->numRoots - 1; $j++) {
$parity[$j] = $parity[$j + 1];
}
if ($feedback !== $this->blockSize) {
$parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
} else {
$parity[$this->numRoots - 1] = 0;
}
}
}
/**
* Decodes received data.
*
* @param SplFixedArray $data
* @param SplFixedArray|null $erasures
* @return null|integer
*/
public function decode(SplFixedArray $data, SplFixedArray $erasures = null)
{
// 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);
$lambda = clone $numRootsPlusOne;
$b = clone $numRootsPlusOne;
$t = clone $numRootsPlusOne;
$omega = clone $numRootsPlusOne;
$root = clone $numRoots;
$loc = clone $numRoots;
$numErasures = ($erasures !== null ? 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++) {
if ($syndromes[$j] === 0) {
$syndromes[$j] = $data[$i];
} else {
$syndromes[$j] = $data[$i] ^ $this->alphaTo[
$this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
];
}
}
}
// Convert syndromes to index form, checking for nonzero conditions
$syndromeError = 0;
for ($i = 0; $i < $this->numRoots; $i++) {
$syndromeError |= $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.
return 0;
}
$lambda[0] = 1;
if ($numErasures > 0) {
// 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++) {
$u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
for ($j = $i + 1; $j > 0; $j--) {
$tmp = $this->indexOf[$lambda[$j - 1]];
if ($tmp !== $this->blockSize) {
$lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
}
}
}
}
for ($i = 0; $i <= $this->numRoots; $i++) {
$b[$i] = $this->indexOf[$lambda[$i]];
}
// Begin Berlekamp-Massey algorithm to determine error+erasure locator
// polynomial
$r = $numErasures;
$el = $numErasures;
while (++$r <= $this->numRoots) {
// Compute discrepancy at the r-th step in poly form
$discrepancyR = 0;
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->indexOf[$discrepancyR];
if ($discrepancyR === $this->blockSize) {
$tmp = $b->toArray();
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];
}
}
if (2 * $el <= $r + $numErasures - 1) {
$el = $r + $numErasures - $el;
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++) {
$lambda[$i] = $this->indexOf[$lambda[$i]];
if ($lambda[$i] !== $this->blockSize) {
$degLambda = $i;
}
}
// Find roots of the error+erasure locator polynomial by Chien search.
$reg = clone $lambda;
$reg[0] = 0;
$count = 0;
for ($i = 1, $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]];
}
}
if ($q !== 0) {
// Not a root
continue;
}
// Store root (index-form) and error location number
$root[$count] = $i;
$loc[$count] = $k;
if (++$count === $degLambda) {
break;
}
}
if ($degLambda !== $count) {
// deg(lambda) unequal to number of roots: uncorreactable 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).
$degOmega = $degLambda - 1;
for ($i = 0; $i <= $degOmega; $i++) {
$tmp = 0;
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])];
}
}
$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--) {
$num1 = 0;
for ($i = $degOmega; $i >= 0; $i--) {
if ($omega[$i] !== $this->blockSize) {
$num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
}
}
$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]
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])];
}
}
// Apply error to data
if ($num1 !== 0 && $loc[$j] >= $this->padding) {
$data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
$this->alphaTo[
$this->modNn(
$this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
)
]
);
}
}
if ($erasures !== null) {
if (count($erasures) < $count) {
$erasures->setSize($count);
}
for ($i = 0; $i < $count; $i++) {
$erasures[$i] = $loc[$i];
}
}
return $count;
}
/**
* Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow
* divide.
*
* @param itneger $x
* @return integer
*/
protected function modNn($x)
{
while ($x >= $this->blockSize) {
$x -= $this->blockSize;
$x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
}
return $x;
}
}

View File

@@ -0,0 +1,687 @@
<?php
/**
* BaconQrCode
*
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
* @copyright 2013 Ben 'DASPRiD' Scholzen
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
*/
namespace BaconQrCode\Common;
use SplFixedArray;
/**
* Version representation.
*/
class Version
{
/**
* Version decode information.
*
* @var array
*/
protected static $versionDecodeInfo = array(
0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
0x27541, 0x28c69
);
/**
* Cached version instances.
*
* @var array
*/
protected static $versions = array();
/**
* Version number of this version.
*
* @var integer
*/
protected $versionNumber;
/**
* Alignment pattern centers.
*
* @var SplFixedArray
*/
protected $alignmentPatternCenters;
/**
* Error correction blocks.
*
* @var SplFixedArray
*/
protected $errorCorrectionBlocks;
/**
* Total number of codewords.
*
* @var integer
*/
protected $totalCodewords;
/**
* Creates a new version.
*
* @param integer $versionNumber
* @param SplFixedArray $alignmentPatternCenters
* @param SplFixedArray $ecBlocks
*/
protected function __construct(
$versionNumber,
SplFixedArray $alignmentPatternCenters,
SplFixedArray $ecBlocks
) {
$this->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)
);
}
}