You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
657 lines
16 KiB
657 lines
16 KiB
<?php
|
|
/**
|
|
* This file provides the main QRCode class.
|
|
*
|
|
* The project is a rewrite from jQuery extension available at
|
|
* @link https://github.com/jeromeetienne/jquery-qrcode
|
|
*
|
|
* For a detailed description of QRCode and its features please check
|
|
* @link http://www.qrcode.com/
|
|
*
|
|
* QR Code is registered trademark of
|
|
* DENSO WAVE INCORPORATED
|
|
* http://www.denso-wave.com/qrcode/faqpatent-e.html
|
|
*
|
|
* All files in the package have the same license:
|
|
* http://opensource.org/licenses/BSD-2-Clause
|
|
*
|
|
* @copyright BSD2
|
|
* @author Maik Greubel <greubel@nkey.de>
|
|
*/
|
|
|
|
/**
|
|
* Import necessary dependencies
|
|
*/
|
|
require_once 'QR8bitByte.php';
|
|
require_once 'QRBitBuffer.php';
|
|
require_once 'QRRSBlock.php';
|
|
require_once 'QRUtil.php';
|
|
require_once 'QRCodeException.php';
|
|
|
|
/**
|
|
* This is the main class
|
|
*
|
|
* It provides the functionality to generate a QRCode bitmap
|
|
* out of appended data elements.
|
|
*
|
|
* @package phpQr
|
|
* @author Maik Greubel <greubel@nkey.de>
|
|
*/
|
|
class QRCode
|
|
{
|
|
/**
|
|
* Needed for padding
|
|
*
|
|
* @final
|
|
*
|
|
*/
|
|
const PAD0 = 0xec;
|
|
|
|
/**
|
|
* Needed for padding
|
|
*
|
|
* @final
|
|
*
|
|
*/
|
|
const PAD1 = 0x11;
|
|
|
|
/**
|
|
* The type number of qrcode
|
|
*
|
|
* @var int
|
|
*/
|
|
private $typeNumber;
|
|
|
|
/**
|
|
* Level of error correction
|
|
*
|
|
* @see QRErrorCorrectLevel
|
|
*
|
|
* @var int
|
|
*/
|
|
private $errorCorrectLevel;
|
|
|
|
/**
|
|
* Bitmap
|
|
*
|
|
* @var array
|
|
*/
|
|
private $modules;
|
|
|
|
/**
|
|
* Amount of modules in bitmap
|
|
*
|
|
* @var int
|
|
*/
|
|
private $moduleCount;
|
|
|
|
/**
|
|
* The data as array
|
|
*
|
|
* @var array
|
|
*/
|
|
private $dataCache;
|
|
|
|
/**
|
|
* All append data elements
|
|
*
|
|
* @var array
|
|
*/
|
|
private $dataList;
|
|
|
|
/**
|
|
* Create a new instance of QRCode
|
|
*
|
|
* @param int $typeNumber
|
|
* The type of QRCode
|
|
* @param int $errorCorrectLevel
|
|
* The error correction level
|
|
*/
|
|
public function __construct($typeNumber, $errorCorrectLevel)
|
|
{
|
|
$this->typeNumber = $typeNumber;
|
|
$this->errorCorrectLevel = $errorCorrectLevel;
|
|
$this->modules = null;
|
|
$this->moduleCount = 0;
|
|
$this->dataCache = null;
|
|
$this->dataList = array ();
|
|
}
|
|
|
|
/**
|
|
* This function is only needed for debugging purposes and returns the bitmap
|
|
* DONT USE THIS TO MANIPULATE THE BITMAP!
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getModules()
|
|
{
|
|
return $this->modules;
|
|
}
|
|
|
|
/**
|
|
* Add a new data element to the QRCode
|
|
*
|
|
* @param string $data
|
|
*/
|
|
public function addData($data)
|
|
{
|
|
$newData = new QR8bitByte ( $data );
|
|
array_push ( $this->dataList, $newData );
|
|
$this->dataCache = null;
|
|
}
|
|
|
|
/**
|
|
* Returns whether a given bitmap entry is dark or not
|
|
*
|
|
* @param int $row
|
|
* The row in bitmap
|
|
* @param int $col
|
|
* The column in bitmap
|
|
*
|
|
* @throws QRCodeException
|
|
* @return true in case of its a dark bit, false otherwise
|
|
*/
|
|
public function isDark($row, $col)
|
|
{
|
|
if ($row < 0 || $this->moduleCount <= $row || $col < 0 || $this->moduleCount <= $col)
|
|
{
|
|
throw new QRCodeException ( "$row,$col" );
|
|
}
|
|
|
|
return $this->modules [$row] [$col];
|
|
}
|
|
|
|
/**
|
|
* Get the amount of modules in bitmap
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getModuleCount()
|
|
{
|
|
return $this->moduleCount;
|
|
}
|
|
|
|
/**
|
|
* Generate the QRCode bitmap
|
|
*/
|
|
public function make()
|
|
{
|
|
if ($this->typeNumber < 1)
|
|
{
|
|
$typeNumber = 1;
|
|
for($typeNumber = 1; $typeNumber < 40; $typeNumber ++)
|
|
{
|
|
$rsBlocks = QRRSBlock::getInstance ()->getRSBlocks ( $typeNumber, $this->errorCorrectLevel );
|
|
|
|
$buffer = new QRBitBuffer ();
|
|
$totalDataCount = 0;
|
|
for($i = 0; $i < sizeof ( $rsBlocks ); $i ++)
|
|
{
|
|
$totalDataCount += $rsBlocks [$i]->getDataCount ();
|
|
}
|
|
|
|
for($i = 0; $i < sizeof ( $this->dataList ); $i ++)
|
|
{
|
|
$data = $this->dataList [$i];
|
|
|
|
assert ( $data instanceof QRByte );
|
|
|
|
$buffer->put ( $data->getMode (), 4 );
|
|
$buffer->put ( $data->getLength (), QRUtil::getInstance ()->getLengthInBits ( $data->getMode (), $typeNumber ) );
|
|
$data->write ( $buffer );
|
|
}
|
|
if ($buffer->getLengthInBits () <= $totalDataCount * 8)
|
|
break;
|
|
}
|
|
$this->typeNumber = $typeNumber;
|
|
}
|
|
$this->makeImpl ( false, $this->getBestMaskPattern () );
|
|
}
|
|
|
|
/**
|
|
* Generates the bitmap (really)
|
|
*
|
|
* @param boolean $test
|
|
* @param int $maskPattern
|
|
*/
|
|
private function makeImpl($test, $maskPattern)
|
|
{
|
|
$this->moduleCount = $this->typeNumber * 4 + 17;
|
|
$this->modules = QRUtil::getInstance ()->createEmptyArray ( $this->moduleCount );
|
|
|
|
for($row = 0; $row < $this->moduleCount; $row ++)
|
|
{
|
|
$this->modules [$row] = QRUtil::getInstance ()->createEmptyArray ( $this->moduleCount );
|
|
|
|
for($col = 0; $col < $this->moduleCount; $col ++)
|
|
{
|
|
$this->modules [$row] [$col] = null;
|
|
}
|
|
}
|
|
|
|
$this->setupPositionProbePattern ( 0, 0 );
|
|
$this->setupPositionProbePattern ( $this->moduleCount - 7, 0 );
|
|
$this->setupPositionProbePattern ( 0, $this->moduleCount - 7 );
|
|
$this->setupPositionAdjustPattern ();
|
|
$this->setupTimingPattern ();
|
|
$this->setupTypeInfo ( $test, $maskPattern );
|
|
|
|
if ($this->typeNumber >= 7)
|
|
{
|
|
$this->setTypeNumber ( $test );
|
|
}
|
|
|
|
if ($this->dataCache == null)
|
|
{
|
|
$this->dataCache = self::createData ( $this->typeNumber, $this->errorCorrectLevel, $this->dataList );
|
|
}
|
|
|
|
$this->mapData ( $this->dataCache, $maskPattern );
|
|
}
|
|
|
|
/**
|
|
* Add the position probes to the bitmap
|
|
*
|
|
* @param int $row
|
|
* @param int $col
|
|
*/
|
|
private function setupPositionProbePattern($row, $col)
|
|
{
|
|
for($r = - 1; $r <= 7; $r ++)
|
|
{
|
|
if ($row + $r <= - 1 || $this->moduleCount <= $row + $r)
|
|
continue;
|
|
|
|
for($c = - 1; $c <= 7; $c ++)
|
|
{
|
|
if ($col + $c <= - 1 || $this->moduleCount <= $col + $c)
|
|
continue;
|
|
|
|
if ((0 <= $r && $r <= 6 && ($c == 0 || $c == 6)) || (0 <= $c && $c <= 6 && ($r == 0 || $r == 6)) || (2 <= $r && $r <= 4 && 2 <= $c && $c <= 4))
|
|
{
|
|
$this->modules [$row + $r] [$col + $c] = true;
|
|
}
|
|
else
|
|
{
|
|
$this->modules [$row + $r] [$col + $c] = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the best mask pattern for this QRCode
|
|
*
|
|
* @return int
|
|
*/
|
|
private function getBestMaskPattern()
|
|
{
|
|
$minLostPoint = 0;
|
|
$pattern = 0;
|
|
|
|
for($i = 0; $i < 8; $i ++)
|
|
{
|
|
$this->makeImpl ( true, $i );
|
|
|
|
$lostPoint = QRUtil::getInstance ()->getLostPoint ( $this );
|
|
|
|
if ($i == 0 || $minLostPoint > $lostPoint)
|
|
{
|
|
$minLostPoint = $lostPoint;
|
|
$pattern = $i;
|
|
}
|
|
}
|
|
|
|
return $pattern;
|
|
}
|
|
|
|
/**
|
|
* Add the timing pattern to bitmap
|
|
*/
|
|
private function setupTimingPattern()
|
|
{
|
|
for($r = 8; $r < $this->moduleCount - 8; $r ++)
|
|
{
|
|
if ($this->modules [$r] [6] != null)
|
|
{
|
|
continue;
|
|
}
|
|
$this->modules [$r] [6] = ($r % 2 == 0);
|
|
}
|
|
|
|
for($c = 8; $c < $this->moduleCount - 8; $c ++)
|
|
{
|
|
if ($this->modules [6] [$c] != null)
|
|
{
|
|
continue;
|
|
}
|
|
$this->modules [6] [$c] = ($c % 2 == 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the position adjust pattern to bitmap
|
|
*/
|
|
private function setupPositionAdjustPattern()
|
|
{
|
|
$pos = QRUtil::getInstance ()->getPatternPosition ( $this->typeNumber );
|
|
|
|
for($i = 0; $i < sizeof ( $pos ); $i ++)
|
|
{
|
|
for($j = 0; $j < sizeof ( $pos ); $j ++)
|
|
{
|
|
$row = $pos [$i];
|
|
$col = $pos [$j];
|
|
|
|
if ($this->modules [$row] [$col] != null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for($r = - 2; $r <= 2; $r ++)
|
|
{
|
|
for($c = - 2; $c <= 2; $c ++)
|
|
{
|
|
if ($r == - 2 || $r == 2 || $c == - 2 || $c == 2 || ($r == 0 && $c == 0))
|
|
{
|
|
$this->modules [$row + $r] [$col + $c] = true;
|
|
}
|
|
else
|
|
{
|
|
$this->modules [$row + $r] [$col + $c] = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the type number to bitmap
|
|
*
|
|
* @param boolean $test
|
|
*/
|
|
private function setTypeNumber($test)
|
|
{
|
|
$bits = QRUtil::getInstance ()->getBCHTypeNumber ( $this->typeNumber );
|
|
|
|
for($i = 0; $i < 18; $i ++)
|
|
{
|
|
$mod = (! $test && (($bits >> $i) & 1) == 1);
|
|
$this->modules [floor ( $i / 3 )] [$i % 3 + $this->moduleCount - 8 - 3] = $mod;
|
|
}
|
|
|
|
for($i = 0; $i < 18; $i ++)
|
|
{
|
|
$mod = (! $test && (($bits >> $i) & 1) == 1);
|
|
$this->modules [$i % 3 + $this->moduleCount - 8 - 3] [floor ( $i / 3 )] = $mod;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the type info to bitmap
|
|
*
|
|
* @param boolean $test
|
|
* @param int $maskPattern
|
|
*/
|
|
private function setupTypeInfo($test, $maskPattern)
|
|
{
|
|
$data = ($this->errorCorrectLevel << 3) | $maskPattern;
|
|
$bits = QRUtil::getInstance ()->getBCHTypeInfo ( $data );
|
|
|
|
// vertical
|
|
for($i = 0; $i < 15; $i ++)
|
|
{
|
|
$mod = (! $test && (($bits >> $i) & 1) == 1);
|
|
if ($i < 6)
|
|
{
|
|
$this->modules [$i] [8] = $mod;
|
|
}
|
|
else if ($i < 8)
|
|
{
|
|
$this->modules [$i + 1] [8] = $mod;
|
|
}
|
|
else
|
|
{
|
|
$this->modules [$this->moduleCount - 15 + $i] [8] = $mod;
|
|
}
|
|
}
|
|
|
|
// horizontal
|
|
for($i = 0; $i < 15; $i ++)
|
|
{
|
|
$mod = (! $test && (($bits >> $i) & 1) == 1);
|
|
|
|
if ($i < 8)
|
|
{
|
|
$this->modules [8] [$this->moduleCount - $i - 1] = $mod;
|
|
}
|
|
else if ($i < 9)
|
|
{
|
|
$this->modules [8] [15 - $i - 1 + 1] = $mod;
|
|
}
|
|
else
|
|
{
|
|
$this->modules [8] [15 - $i - 1] = $mod;
|
|
}
|
|
}
|
|
|
|
// fixed module
|
|
$this->modules [$this->moduleCount - 8] [8] = (! $test);
|
|
}
|
|
|
|
/**
|
|
* Add the data to bitmap
|
|
*
|
|
* @param array $data
|
|
* @param int $maskPattern
|
|
*/
|
|
private function mapData($data, $maskPattern)
|
|
{
|
|
$inc = - 1;
|
|
$row = $this->moduleCount - 1;
|
|
$bitIndex = 7;
|
|
$byteIndex = 0;
|
|
|
|
for($col = $this->moduleCount - 1; $col > 0; $col -= 2)
|
|
{
|
|
if ($col == 6)
|
|
$col --;
|
|
|
|
while ( true )
|
|
{
|
|
for($c = 0; $c < 2; $c ++)
|
|
{
|
|
if ($this->modules [$row] [$col - $c] === null)
|
|
{
|
|
$dark = false;
|
|
|
|
if ($byteIndex < sizeof ( $data ))
|
|
{
|
|
$dark = ((($data [$byteIndex] >> $bitIndex) & 1) == 1);
|
|
}
|
|
|
|
$mask = QRUtil::getInstance ()->getMask ( $maskPattern, $row, $col - $c );
|
|
|
|
if ($mask)
|
|
$dark = ! $dark;
|
|
|
|
$this->modules [$row] [$col - $c] = $dark;
|
|
$bitIndex --;
|
|
|
|
if ($bitIndex == - 1)
|
|
{
|
|
$byteIndex ++;
|
|
$bitIndex = 7;
|
|
}
|
|
}
|
|
}
|
|
|
|
$row += $inc;
|
|
|
|
if ($row < 0 || $this->moduleCount <= $row)
|
|
{
|
|
$row -= $inc;
|
|
$inc = - $inc;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a bitmap out of all append data elements
|
|
*
|
|
* @param int $typeNumber
|
|
* @param int $errorCorrectLevel
|
|
* @param array $dataList
|
|
*
|
|
* @throws QRCodeException
|
|
*
|
|
* @return array
|
|
*/
|
|
private function createData($typeNumber, $errorCorrectLevel, $dataList)
|
|
{
|
|
$rsBlocks = QRRSBlock::getInstance ()->getRSBlocks ( $typeNumber, $errorCorrectLevel );
|
|
|
|
$buffer = new QRBitBuffer ();
|
|
|
|
for($i = 0; $i < sizeof ( $dataList ); $i ++)
|
|
{
|
|
$data = $dataList [$i];
|
|
assert ( $data instanceof QRByte );
|
|
|
|
$buffer->put ( $data->getMode (), 4 );
|
|
$buffer->put ( $data->getLength (), QRUtil::getInstance ()->getLengthInBits ( $data->getMode (), $typeNumber ) );
|
|
$data->write ( $buffer );
|
|
}
|
|
|
|
// calc num max data
|
|
$totalDataCount = 0;
|
|
for($i = 0; $i < sizeof ( $rsBlocks ); $i ++)
|
|
{
|
|
$totalDataCount += $rsBlocks [$i]->getDataCount ();
|
|
}
|
|
|
|
if ($buffer->getLengthInBits () > $totalDataCount * 8)
|
|
{
|
|
throw new QRCodeException ( "code length overflow (" . $buffer->getLengthInBits () . " > " . ($totalDataCount * 8) . ")" );
|
|
}
|
|
|
|
// end code
|
|
if ($buffer->getLengthInBits () + 4 <= $totalDataCount * 8)
|
|
{
|
|
$buffer->put ( 0, 4 );
|
|
}
|
|
|
|
// padding
|
|
while ( $buffer->getLengthInBits () % 8 != 0 )
|
|
{
|
|
$buffer->putBit ( false );
|
|
}
|
|
|
|
// padding
|
|
while ( true )
|
|
{
|
|
if ($buffer->getLengthInBits () >= $totalDataCount * 8)
|
|
{
|
|
break;
|
|
}
|
|
|
|
$buffer->put ( QRCode::PAD0, 8 );
|
|
|
|
if ($buffer->getLengthInBits () >= $totalDataCount * 8)
|
|
{
|
|
break;
|
|
}
|
|
$buffer->put ( QRCode::PAD1, 8 );
|
|
}
|
|
|
|
return $this->createBytes ( $buffer, $rsBlocks );
|
|
}
|
|
|
|
/**
|
|
* Create bitmap out of the bit buffer using reed solomon blocks
|
|
*
|
|
* @param QRBitBuffer $buffer
|
|
* @param array $rsBlocks
|
|
* @return array
|
|
*/
|
|
public function createBytes(QRBitBuffer $buffer, $rsBlocks)
|
|
{
|
|
$offset = 0;
|
|
$maxDcCount = 0;
|
|
$maxEcCount = 0;
|
|
|
|
$dcdata = QRUtil::getInstance ()->createEmptyArray ( sizeof ( $rsBlocks ) );
|
|
$ecdata = QRUtil::getInstance ()->createEmptyArray ( sizeof ( $rsBlocks ) );
|
|
|
|
for($r = 0; $r < sizeof ( $rsBlocks ); $r ++)
|
|
{
|
|
$dcCount = $rsBlocks [$r]->getDataCount ();
|
|
$ecCount = $rsBlocks [$r]->getTotalCount () - $dcCount;
|
|
|
|
$maxDcCount = max ( array (
|
|
$maxDcCount,
|
|
$dcCount
|
|
) );
|
|
$maxEcCount = max ( array (
|
|
$maxEcCount,
|
|
$ecCount
|
|
) );
|
|
|
|
$dcdata [$r] = QRUtil::getInstance ()->createEmptyArray ( $dcCount );
|
|
|
|
for($i = 0; $i < sizeof ( $dcdata [$r] ); $i ++)
|
|
{
|
|
$dcdata [$r] [$i] = 0xff & $buffer->getAt ( $i + $offset );
|
|
}
|
|
$offset += $dcCount;
|
|
|
|
$rsPoly = QRUtil::getInstance ()->getErrorCorrectPolynominal ( $ecCount );
|
|
$rawPoly = new QRPolynominal ( $dcdata [$r], $rsPoly->getLength () - 1 );
|
|
|
|
$modPoly = $rawPoly->mod ( $rsPoly );
|
|
$ecdata [$r] = QRUtil::getInstance ()->createEmptyArray ( $rsPoly->getLength () - 1 );
|
|
for($i = 0; $i < sizeof ( $ecdata [$r] ); $i ++)
|
|
{
|
|
$modIndex = $i + $modPoly->getLength () - sizeof ( $ecdata [$r] );
|
|
$ecdata [$r] [$i] = ($modIndex >= 0) ? $modPoly->get ( $modIndex ) : 0;
|
|
}
|
|
}
|
|
|
|
$totalCodeCount = 0;
|
|
for($i = 0; $i < sizeof ( $rsBlocks ); $i ++)
|
|
{
|
|
$totalCodeCount += $rsBlocks [$i]->getTotalCount ();
|
|
}
|
|
|
|
$data = QRUtil::getInstance ()->createEmptyArray ( $totalCodeCount );
|
|
$index = 0;
|
|
|
|
for($i = 0; $i < $maxDcCount; $i ++)
|
|
{
|
|
for($r = 0; $r < sizeof ( $rsBlocks ); $r ++)
|
|
{
|
|
if ($i < sizeof ( $dcdata [$r] ))
|
|
{
|
|
$data [$index ++] = $dcdata [$r] [$i];
|
|
}
|
|
}
|
|
}
|
|
|
|
for($i = 0; $i < $maxEcCount; $i ++)
|
|
{
|
|
for($r = 0; $r < sizeof ( $rsBlocks ); $r ++)
|
|
{
|
|
if ($i < sizeof ( $ecdata [$r] ))
|
|
{
|
|
$data [$index ++] = $ecdata [$r] [$i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
//yea will need to inspect this for the ldap qr code
|