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.
502 lines
17 KiB
502 lines
17 KiB
<?php |
|
/* |
|
* PHP QR Code encoder |
|
* |
|
* Main encoder classes. |
|
* |
|
* Based on libqrencode C library distributed under LGPL 2.1 |
|
* Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net> |
|
* |
|
* PHP QR Code is distributed under LGPL 3 |
|
* Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm> |
|
* |
|
* This library is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU Lesser General Public |
|
* License as published by the Free Software Foundation; either |
|
* version 3 of the License, or any later version. |
|
* |
|
* This library is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* Lesser General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Lesser General Public |
|
* License along with this library; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
*/ |
|
|
|
class QRrsblock { |
|
public $dataLength; |
|
public $data = array(); |
|
public $eccLength; |
|
public $ecc = array(); |
|
|
|
public function __construct($dl, $data, $el, &$ecc, QRrsItem $rs) |
|
{ |
|
$rs->encode_rs_char($data, $ecc); |
|
|
|
$this->dataLength = $dl; |
|
$this->data = $data; |
|
$this->eccLength = $el; |
|
$this->ecc = $ecc; |
|
} |
|
}; |
|
|
|
//########################################################################## |
|
|
|
class QRrawcode { |
|
public $version; |
|
public $datacode = array(); |
|
public $ecccode = array(); |
|
public $blocks; |
|
public $rsblocks = array(); //of RSblock |
|
public $count; |
|
public $dataLength; |
|
public $eccLength; |
|
public $b1; |
|
|
|
//---------------------------------------------------------------------- |
|
public function __construct(QRinput $input) |
|
{ |
|
$spec = array(0,0,0,0,0); |
|
|
|
$this->datacode = $input->getByteStream(); |
|
if(is_null($this->datacode)) { |
|
throw new Exception('null imput string'); |
|
} |
|
|
|
QRspec::getEccSpec($input->getVersion(), $input->getErrorCorrectionLevel(), $spec); |
|
|
|
$this->version = $input->getVersion(); |
|
$this->b1 = QRspec::rsBlockNum1($spec); |
|
$this->dataLength = QRspec::rsDataLength($spec); |
|
$this->eccLength = QRspec::rsEccLength($spec); |
|
$this->ecccode = array_fill(0, $this->eccLength, 0); |
|
$this->blocks = QRspec::rsBlockNum($spec); |
|
|
|
$ret = $this->init($spec); |
|
if($ret < 0) { |
|
throw new Exception('block alloc error'); |
|
return null; |
|
} |
|
|
|
$this->count = 0; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function init(array $spec) |
|
{ |
|
$dl = QRspec::rsDataCodes1($spec); |
|
$el = QRspec::rsEccCodes1($spec); |
|
$rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el); |
|
|
|
|
|
$blockNo = 0; |
|
$dataPos = 0; |
|
$eccPos = 0; |
|
for($i=0; $i<QRspec::rsBlockNum1($spec); $i++) { |
|
$ecc = array_slice($this->ecccode,$eccPos); |
|
$this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs); |
|
$this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc); |
|
|
|
$dataPos += $dl; |
|
$eccPos += $el; |
|
$blockNo++; |
|
} |
|
|
|
if(QRspec::rsBlockNum2($spec) == 0) |
|
return 0; |
|
|
|
$dl = QRspec::rsDataCodes2($spec); |
|
$el = QRspec::rsEccCodes2($spec); |
|
$rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el); |
|
|
|
if($rs == NULL) return -1; |
|
|
|
for($i=0; $i<QRspec::rsBlockNum2($spec); $i++) { |
|
$ecc = array_slice($this->ecccode,$eccPos); |
|
$this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs); |
|
$this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc); |
|
|
|
$dataPos += $dl; |
|
$eccPos += $el; |
|
$blockNo++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function getCode() |
|
{ |
|
$ret; |
|
|
|
if($this->count < $this->dataLength) { |
|
$row = $this->count % $this->blocks; |
|
$col = $this->count / $this->blocks; |
|
if($col >= $this->rsblocks[0]->dataLength) { |
|
$row += $this->b1; |
|
} |
|
$ret = $this->rsblocks[$row]->data[$col]; |
|
} else if($this->count < $this->dataLength + $this->eccLength) { |
|
$row = ($this->count - $this->dataLength) % $this->blocks; |
|
$col = ($this->count - $this->dataLength) / $this->blocks; |
|
$ret = $this->rsblocks[$row]->ecc[$col]; |
|
} else { |
|
return 0; |
|
} |
|
$this->count++; |
|
|
|
return $ret; |
|
} |
|
} |
|
|
|
//########################################################################## |
|
|
|
class QRcode { |
|
|
|
public $version; |
|
public $width; |
|
public $data; |
|
|
|
//---------------------------------------------------------------------- |
|
public function encodeMask(QRinput $input, $mask) |
|
{ |
|
if($input->getVersion() < 0 || $input->getVersion() > QRSPEC_VERSION_MAX) { |
|
throw new Exception('wrong version'); |
|
} |
|
if($input->getErrorCorrectionLevel() > QR_ECLEVEL_H) { |
|
throw new Exception('wrong level'); |
|
} |
|
|
|
$raw = new QRrawcode($input); |
|
|
|
QRtools::markTime('after_raw'); |
|
|
|
$version = $raw->version; |
|
$width = QRspec::getWidth($version); |
|
$frame = QRspec::newFrame($version); |
|
|
|
$filler = new FrameFiller($width, $frame); |
|
if(is_null($filler)) { |
|
return NULL; |
|
} |
|
|
|
// inteleaved data and ecc codes |
|
for($i=0; $i<$raw->dataLength + $raw->eccLength; $i++) { |
|
$code = $raw->getCode(); |
|
$bit = 0x80; |
|
for($j=0; $j<8; $j++) { |
|
$addr = $filler->next(); |
|
$filler->setFrameAt($addr, 0x02 | (($bit & $code) != 0)); |
|
$bit = $bit >> 1; |
|
} |
|
} |
|
|
|
QRtools::markTime('after_filler'); |
|
|
|
unset($raw); |
|
|
|
// remainder bits |
|
$j = QRspec::getRemainder($version); |
|
for($i=0; $i<$j; $i++) { |
|
$addr = $filler->next(); |
|
$filler->setFrameAt($addr, 0x02); |
|
} |
|
|
|
$frame = $filler->frame; |
|
unset($filler); |
|
|
|
|
|
// masking |
|
$maskObj = new QRmask(); |
|
if($mask < 0) { |
|
|
|
if (QR_FIND_BEST_MASK) { |
|
$masked = $maskObj->mask($width, $frame, $input->getErrorCorrectionLevel()); |
|
} else { |
|
$masked = $maskObj->makeMask($width, $frame, (intval(QR_DEFAULT_MASK) % 8), $input->getErrorCorrectionLevel()); |
|
} |
|
} else { |
|
$masked = $maskObj->makeMask($width, $frame, $mask, $input->getErrorCorrectionLevel()); |
|
} |
|
|
|
if($masked == NULL) { |
|
return NULL; |
|
} |
|
|
|
QRtools::markTime('after_mask'); |
|
|
|
$this->version = $version; |
|
$this->width = $width; |
|
$this->data = $masked; |
|
|
|
return $this; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function encodeInput(QRinput $input) |
|
{ |
|
return $this->encodeMask($input, -1); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function encodeString8bit($string, $version, $level) |
|
{ |
|
if(string == NULL) { |
|
throw new Exception('empty string!'); |
|
return NULL; |
|
} |
|
|
|
$input = new QRinput($version, $level); |
|
if($input == NULL) return NULL; |
|
|
|
$ret = $input->append($input, QR_MODE_8, strlen($string), str_split($string)); |
|
if($ret < 0) { |
|
unset($input); |
|
return NULL; |
|
} |
|
return $this->encodeInput($input); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function encodeString($string, $version, $level, $hint, $casesensitive) |
|
{ |
|
|
|
if($hint != QR_MODE_8 && $hint != QR_MODE_KANJI) { |
|
throw new Exception('bad hint'); |
|
return NULL; |
|
} |
|
|
|
$input = new QRinput($version, $level); |
|
if($input == NULL) return NULL; |
|
|
|
$ret = QRsplit::splitStringToQRinput($string, $input, $hint, $casesensitive); |
|
if($ret < 0) { |
|
return NULL; |
|
} |
|
|
|
return $this->encodeInput($input); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public static function png($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false) |
|
{ |
|
$enc = QRencode::factory($level, $size, $margin); |
|
return $enc->encodePNG($text, $outfile, $saveandprint=false); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public static function text($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) |
|
{ |
|
$enc = QRencode::factory($level, $size, $margin); |
|
return $enc->encode($text, $outfile); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public static function raw($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) |
|
{ |
|
$enc = QRencode::factory($level, $size, $margin); |
|
return $enc->encodeRAW($text, $outfile); |
|
} |
|
} |
|
|
|
//########################################################################## |
|
|
|
class FrameFiller { |
|
|
|
public $width; |
|
public $frame; |
|
public $x; |
|
public $y; |
|
public $dir; |
|
public $bit; |
|
|
|
//---------------------------------------------------------------------- |
|
public function __construct($width, &$frame) |
|
{ |
|
$this->width = $width; |
|
$this->frame = $frame; |
|
$this->x = $width - 1; |
|
$this->y = $width - 1; |
|
$this->dir = -1; |
|
$this->bit = -1; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function setFrameAt($at, $val) |
|
{ |
|
$this->frame[$at['y']][$at['x']] = chr($val); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function getFrameAt($at) |
|
{ |
|
return ord($this->frame[$at['y']][$at['x']]); |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function next() |
|
{ |
|
do { |
|
|
|
if($this->bit == -1) { |
|
$this->bit = 0; |
|
return array('x'=>$this->x, 'y'=>$this->y); |
|
} |
|
|
|
$x = $this->x; |
|
$y = $this->y; |
|
$w = $this->width; |
|
|
|
if($this->bit == 0) { |
|
$x--; |
|
$this->bit++; |
|
} else { |
|
$x++; |
|
$y += $this->dir; |
|
$this->bit--; |
|
} |
|
|
|
if($this->dir < 0) { |
|
if($y < 0) { |
|
$y = 0; |
|
$x -= 2; |
|
$this->dir = 1; |
|
if($x == 6) { |
|
$x--; |
|
$y = 9; |
|
} |
|
} |
|
} else { |
|
if($y == $w) { |
|
$y = $w - 1; |
|
$x -= 2; |
|
$this->dir = -1; |
|
if($x == 6) { |
|
$x--; |
|
$y -= 8; |
|
} |
|
} |
|
} |
|
if($x < 0 || $y < 0) return null; |
|
|
|
$this->x = $x; |
|
$this->y = $y; |
|
|
|
} while(ord($this->frame[$y][$x]) & 0x80); |
|
|
|
return array('x'=>$x, 'y'=>$y); |
|
} |
|
|
|
} ; |
|
|
|
//########################################################################## |
|
|
|
class QRencode { |
|
|
|
public $casesensitive = true; |
|
public $eightbit = false; |
|
|
|
public $version = 0; |
|
public $size = 3; |
|
public $margin = 4; |
|
|
|
public $structured = 0; // not supported yet |
|
|
|
public $level = QR_ECLEVEL_L; |
|
public $hint = QR_MODE_8; |
|
|
|
//---------------------------------------------------------------------- |
|
public static function factory($level = QR_ECLEVEL_L, $size = 3, $margin = 4) |
|
{ |
|
$enc = new QRencode(); |
|
$enc->size = $size; |
|
$enc->margin = $margin; |
|
|
|
switch ($level.'') { |
|
case '0': |
|
case '1': |
|
case '2': |
|
case '3': |
|
$enc->level = $level; |
|
break; |
|
case 'l': |
|
case 'L': |
|
$enc->level = QR_ECLEVEL_L; |
|
break; |
|
case 'm': |
|
case 'M': |
|
$enc->level = QR_ECLEVEL_M; |
|
break; |
|
case 'q': |
|
case 'Q': |
|
$enc->level = QR_ECLEVEL_Q; |
|
break; |
|
case 'h': |
|
case 'H': |
|
$enc->level = QR_ECLEVEL_H; |
|
break; |
|
} |
|
|
|
return $enc; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function encodeRAW($intext, $outfile = false) |
|
{ |
|
$code = new QRcode(); |
|
|
|
if($this->eightbit) { |
|
$code->encodeString8bit($intext, $this->version, $this->level); |
|
} else { |
|
$code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive); |
|
} |
|
|
|
return $code->data; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function encode($intext, $outfile = false) |
|
{ |
|
$code = new QRcode(); |
|
|
|
if($this->eightbit) { |
|
$code->encodeString8bit($intext, $this->version, $this->level); |
|
} else { |
|
$code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive); |
|
} |
|
|
|
QRtools::markTime('after_encode'); |
|
|
|
if ($outfile!== false) { |
|
file_put_contents($outfile, join("\n", QRtools::binarize($code->data))); |
|
} else { |
|
return QRtools::binarize($code->data); |
|
} |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
public function encodePNG($intext, $outfile = false,$saveandprint=false) |
|
{ |
|
try { |
|
|
|
ob_start(); |
|
$tab = $this->encode($intext); |
|
$err = ob_get_contents(); |
|
ob_end_clean(); |
|
|
|
if ($err != '') |
|
QRtools::log($outfile, $err); |
|
|
|
$maxSize = (int)(QR_PNG_MAXIMUM_SIZE / (count($tab)+2*$this->margin)); |
|
|
|
QRimage::png($tab, $outfile, min(max(1, $this->size), $maxSize), $this->margin,$saveandprint); |
|
|
|
} catch (Exception $e) { |
|
|
|
QRtools::log($outfile, $e->getMessage()); |
|
|
|
} |
|
} |
|
}
|
|
|