diff --git a/composer.json b/composer.json
index cc3bbc586..cf7997c6b 100644
--- a/composer.json
+++ b/composer.json
@@ -18,7 +18,8 @@
}
],
"require": {
- "php": ">=7.0.3"
+ "php": ">=7.0.3",
+ "chillerlan/php-traits": "1.0.*"
},
"require-dev": {
"chillerlan/php-googleauth": "1.1.*",
@@ -34,6 +35,7 @@
},
"autoload-dev": {
"psr-4": {
+ "chillerlan\\QRCodePublic\\": "public/",
"chillerlan\\QRCodeTest\\": "tests/"
}
}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 000000000..f7cf8dc96
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,164 @@
+
+
+
+
+
+ QR Code Generator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/qrcode.php b/public/qrcode.php
new file mode 100644
index 000000000..480fdfb91
--- /dev/null
+++ b/public/qrcode.php
@@ -0,0 +1,90 @@
+
+ * @copyright 2017 Smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCodePublic;
+
+use chillerlan\QRCode\QRCode;
+use chillerlan\QRCode\QROptions;
+
+require_once '../vendor/autoload.php';
+
+try{
+
+ $moduleValues = [
+ // finder
+ 1536 => $_POST['m_finder_dark'],
+ 6 => $_POST['m_finder_light'],
+ // alignment
+ 2560 => $_POST['m_alignment_dark'],
+ 10 => $_POST['m_alignment_light'],
+ // timing
+ 3072 => $_POST['m_timing_dark'],
+ 12 => $_POST['m_timing_light'],
+ // format
+ 3584 => $_POST['m_format_dark'],
+ 14 => $_POST['m_format_light'],
+ // version
+ 4096 => $_POST['m_version_dark'],
+ 16 => $_POST['m_version_light'],
+ // data
+ 1024 => $_POST['m_data_dark'],
+ 4 => $_POST['m_data_light'],
+ // darkmodule
+ 512 => $_POST['m_darkmodule_dark'],
+ // separator
+ 8 => $_POST['m_separator_light'],
+ // quietzone
+ 18 => $_POST['m_quietzone_light'],
+ ];
+
+ $moduleValues = array_map(function($v){
+ #var_dump(array_map('hexdec', unpack('a2r/a2g/a2b','ff00ff')));
+
+ if(preg_match('/[a-f\d]{6}/i', $v) === 1){
+ return '#'.$v;
+ }
+
+ return strpos($v, '_dark') !== false
+ ? '#111111'
+ : '#eeeeee';
+
+ }, $moduleValues);
+
+
+ $ecc = in_array($_POST['ecc'], ['L', 'M', 'Q', 'H'], true) ? $_POST['ecc'] : 'L';
+
+ $qro = new QROptions;
+
+ $qro->version = (int)$_POST['version'];
+ $qro->eccLevel = constant('chillerlan\\QRCode\\QRCode::ECC_'.$ecc);
+ $qro->moduleValues = $moduleValues;
+ $qro->outputType = $_POST['output_type'];
+ $qro->maskPattern = (int)$_POST['maskpattern'];
+ $qro->addQuietzone = isset($_POST['quietzone']);
+ $qro->quietzoneSize = (int)$_POST['quietzonesize'];
+ $qro->scale = (int)$_POST['scale'];
+
+ $qrcode = (new QRCode($qro))->render($_POST['inputstring']);
+
+ send_response(['qrcode' => $qrcode]);
+}
+// Pokรฉmon exception handler
+catch(\Exception $e){
+ header('HTTP/1.1 500 Internal Server Error');
+ send_response(['error' => $e->getMessage()]);
+}
+
+/**
+ * @param array $response
+ */
+function send_response(array $response){
+ header('Content-type: application/json;charset=utf-8;');
+ echo json_encode($response);
+ exit;
+}
diff --git a/src/Container.php b/src/Container.php
deleted file mode 100644
index fa6ed3102..000000000
--- a/src/Container.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
- * @copyright 2017 Smiley
- * @license MIT
- */
-
-namespace chillerlan\QRCode;
-
-/**
- * a generic container with getter and setter
- * @codeCoverageIgnore
- */
-trait Container{
-
- /**
- * Boa constructor.
- *
- * @param array $properties
- */
- public function __construct(array $properties = []){
-
- foreach($properties as $key => $value){
- $this->__set($key, $value);
- }
-
- }
-
- /**
- * David Getter
- *
- * @param string $property
- *
- * @return mixed
- */
- public function __get(string $property){
-
- if(property_exists($this, $property)){
- return $this->{$property};
- }
-
- return false;
- }
-
- /**
- * Jet-setter
- *
- * @param string $property
- * @param mixed $value
- *
- * @return void
- */
- public function __set(string $property, $value){
-
- if(property_exists($this, $property)){
- $this->{$property} = $value;
- }
-
- }
-
-}
diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php
index 333da7154..70dcf01aa 100644
--- a/src/Data/AlphaNum.php
+++ b/src/Data/AlphaNum.php
@@ -12,29 +12,26 @@
namespace chillerlan\QRCode\Data;
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
/**
- *
+ * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
*/
class AlphaNum extends QRDataAbstract{
const CHAR_MAP = [
- 36 => ' ',
- 37 => '$',
- 38 => '%',
- 39 => '*',
- 40 => '+',
- 41 => '-',
- 42 => '.',
- 43 => '/',
- 44 => ':',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*',
+ '+', '-', '.', '/', ':',
];
/**
* @var int
*/
- public $mode = self::MODE_ALPHANUM;
+ protected $datamode = QRCode::DATA_ALPHANUM;
/**
* @var array
@@ -42,20 +39,16 @@ class AlphaNum extends QRDataAbstract{
protected $lengthBits = [9, 11, 13];
/**
- * @param \chillerlan\QRCode\BitBuffer $buffer
- *
- * @return void
+ * @inheritdoc
*/
- public function write(BitBuffer &$buffer){
- $i = 0;
+ protected function write(string $data){
- while($i + 1 < $this->dataLength){
- $buffer->put($this->getCharCode($this->data[$i]) * 45 + $this->getCharCode($this->data[$i + 1]), 11);
- $i += 2;
+ for($i = 0; $i + 1 < $this->strlen; $i += 2){
+ $this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11);
}
- if($i < $this->dataLength){
- $buffer->put($this->getCharCode($this->data[$i]), 6);
+ if($i < $this->strlen){
+ $this->bitBuffer->put($this->getCharCode($data[$i]), 6);
}
}
@@ -66,21 +59,14 @@ class AlphaNum extends QRDataAbstract{
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
- private static function getCharCode(string $chr):int {
- $chr = ord($chr);
+ protected function getCharCode(string $chr):int {
+ $i = array_search($chr, self::CHAR_MAP);
- switch(true){
- case ord('0') <= $chr && $chr <= ord('9'): return $chr - ord('0');
- case ord('A') <= $chr && $chr <= ord('Z'): return $chr - ord('A') + 10;
- default:
- foreach(self::CHAR_MAP as $i => $c){
- if(ord($c) === $chr){
- return $i;
- }
- }
+ if($i !== false){
+ return $i;
}
- throw new QRCodeDataException('illegal char: '.$chr);
+ throw new QRCodeDataException('illegal char: "'.$chr.'" ['.ord($chr).']');
}
}
diff --git a/src/Data/Byte.php b/src/Data/Byte.php
index aa60f4405..e7cbfa448 100644
--- a/src/Data/Byte.php
+++ b/src/Data/Byte.php
@@ -12,17 +12,17 @@
namespace chillerlan\QRCode\Data;
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
/**
- *
+ * Byte mode, ISO-8859-1 or UTF-8
*/
class Byte extends QRDataAbstract{
/**
* @var int
*/
- public $mode = self::MODE_BYTE;
+ protected $datamode = QRCode::DATA_BYTE;
/**
* @var array
@@ -30,16 +30,16 @@ class Byte extends QRDataAbstract{
protected $lengthBits = [8, 16, 16];
/**
- * @param \chillerlan\QRCode\BitBuffer $buffer
- *
- * @return void
+ * @inheritdoc
*/
- public function write(BitBuffer &$buffer){
+ protected function write(string $data){
$i = 0;
- while($i < $this->dataLength){
- $buffer->put(ord($this->data[$i]), 8);
+
+ while($i < $this->strlen){
+ $this->bitBuffer->put(ord($data[$i]), 8);
$i++;
}
+
}
}
diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php
index acdb10811..8cad909e4 100644
--- a/src/Data/Kanji.php
+++ b/src/Data/Kanji.php
@@ -12,17 +12,17 @@
namespace chillerlan\QRCode\Data;
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
/**
- *
+ * Kanji mode: double-byte characters from the Shift JIS character set
*/
class Kanji extends QRDataAbstract{
/**
* @var int
*/
- public $mode = self::MODE_KANJI;
+ protected $datamode = QRCode::DATA_KANJI;
/**
* @var array
@@ -30,32 +30,39 @@ class Kanji extends QRDataAbstract{
protected $lengthBits = [8, 10, 12];
/**
- * @param \chillerlan\QRCode\BitBuffer $buffer
+ * @inheritdoc
+ */
+ protected function getLength(string $data):int{
+ return mb_strlen($data, 'SJIS');
+ }
+
+ /**
+ * @param string $data
*
* @return void
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
- public function write(BitBuffer &$buffer){
+ protected function write(string $data){
+ $len = strlen($data);
- $i = 0;
- while($i + 1 < $this->dataLength){
- $c = ((0xff&ord($this->data[$i])) << 8)|(0xff&ord($this->data[$i + 1]));
+ for($i = 0; $i + 1 < $len; $i += 2){
+ $c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
if(0x8140 <= $c && $c <= 0x9FFC){
$c -= 0x8140;
}
- else if(0xE040 <= $c && $c <= 0xEBBF){
+ elseif(0xE040 <= $c && $c <= 0xEBBF){
$c -= 0xC140;
}
else{
throw new QRCodeDataException('illegal char at '.($i + 1).' ('.$c.')');
}
- $buffer->put((($c >> 8)&0xff) * 0xC0 + ($c&0xff), 13);
- $i += 2;
+ $this->bitBuffer->put((($c >> 8) & 0xff) * 0xC0 + ($c & 0xff), 13);
+
}
- if($i < $this->dataLength){
+ if($i < $len){
throw new QRCodeDataException('illegal char at '.($i + 1));
}
diff --git a/src/Data/MaskPatternTester.php b/src/Data/MaskPatternTester.php
new file mode 100644
index 000000000..10a14fcc0
--- /dev/null
+++ b/src/Data/MaskPatternTester.php
@@ -0,0 +1,215 @@
+
+ * @copyright 2017 Smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Data;
+
+/**
+ * @link http://www.thonky.com/qr-code-tutorial/data-masking
+ */
+class MaskPatternTester{
+
+ /**
+ * @var \chillerlan\QRCode\Data\QRMatrix
+ */
+ protected $matrix;
+
+ /**
+ * @var int
+ */
+ protected $moduleCount;
+
+ /**
+ * @param \chillerlan\QRCode\Data\QRMatrix $matrix
+ */
+ public function setMatrix(QRMatrix $matrix){
+ $this->matrix = $matrix;
+ $this->moduleCount = $this->matrix->size();
+ }
+
+ /**
+ * Returns the penalty for the given mask pattern
+ *
+ * @see \chillerlan\QRCode\QRCode::getBestMaskPattern()
+ *
+ * @return float
+ */
+ public function testPattern():float{
+ $penalty = 0;
+
+ for($level = 1; $level <= 4; $level++){
+ $penalty += call_user_func([$this, 'testLevel'.$level]);
+ }
+
+ return $penalty;
+ }
+
+ /**
+ * Checks for each group of five or more same-colored modules in a row (or column)
+ *
+ * @return float
+ */
+ protected function testLevel1():float{
+ $penalty = 0;
+
+ foreach($this->matrix->matrix() as $y => $row){
+ foreach($row as $x => $val){
+ $count = 0;
+
+ for($ry = -1; $ry <= 1; $ry++){
+
+ if($y + $ry < 0 || $this->moduleCount <= $y + $ry){
+ continue;
+ }
+
+ for($rx = -1; $rx <= 1; $rx++){
+
+ if(($ry === 0 && $rx === 0) || ($x + $rx < 0 || $this->moduleCount <= $x + $rx)){
+ continue;
+ }
+
+ if($this->matrix->check($x + $rx, $y + $ry) === ($val >> 8 > 0)){
+ $count++;
+ }
+
+ }
+ }
+
+ if($count > 5){
+ $penalty += (3 + $count - 5);
+ }
+
+ }
+ }
+
+ return $penalty;
+ }
+
+ /**
+ * Checks for each 2x2 area of same-colored modules in the matrix
+ *
+ * @return float
+ */
+ protected function testLevel2():float{
+ $penalty = 0;
+
+ foreach($this->matrix->matrix() as $y => $row){
+
+ if($y > $this->moduleCount - 2){
+ break;
+ }
+
+ foreach($row as $x => $val){
+
+ if($x > $this->moduleCount - 2){
+ break;
+ }
+
+ $count = 0;
+
+ if($val >> 8 > 0){
+ $count++;
+ }
+
+ if($this->matrix->check($y, $x + 1)){
+ $count++;
+ }
+
+ if($this->matrix->check($y + 1, $x)){
+ $count++;
+ }
+
+ if($this->matrix->check($y + 1, $x + 1)){
+ $count++;
+ }
+
+ if($count === 0 || $count === 4){
+ $penalty += 3;
+ }
+
+ }
+ }
+
+ return $penalty;
+ }
+
+ /**
+ * Checks if there are patterns that look similar to the finder patterns
+ *
+ * @return float
+ */
+ protected function testLevel3():float{
+ $penalty = 0;
+
+ foreach($this->matrix->matrix() as $y => $row){
+ foreach($row as $x => $val){
+
+ if($x > $this->moduleCount - 7){
+ break;
+ }
+
+ if(
+ $val >> 8 > 0
+ && !$this->matrix->check($x + 1, $y)
+ && $this->matrix->check($x + 2, $y)
+ && $this->matrix->check($x + 3, $y)
+ && $this->matrix->check($x + 4, $y)
+ && !$this->matrix->check($x + 5, $y)
+ && $this->matrix->check($x + 6, $y)
+ ){
+ $penalty += 40;
+ }
+
+ }
+ }
+
+ for ($x = 0; $x < $this->moduleCount; $x++) {
+ for ($y = 0; $y < $this->moduleCount - 6; $y++) {
+
+ if(
+ $this->matrix->check($x, $y )
+ && !$this->matrix->check($x, $y + 1)
+ && $this->matrix->check($x, $y + 2)
+ && $this->matrix->check($x, $y + 3)
+ && $this->matrix->check($x, $y + 4)
+ && !$this->matrix->check($x, $y + 5)
+ && $this->matrix->check($x, $y + 6)
+ ){
+ $penalty += 40;
+ }
+
+ }
+ }
+
+ return $penalty;
+ }
+
+ /**
+ * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
+ *
+ * @return float
+ */
+ protected function testLevel4():float {
+ $count = 0;
+
+ foreach($this->matrix->matrix() as $y => $row){
+ foreach($row as $x => $val){
+ if($val >> 8 > 0){
+ $count++;
+ }
+ }
+ }
+
+ return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10;
+ }
+
+
+}
diff --git a/src/Data/Number.php b/src/Data/Number.php
index d8b90a16b..1a946154a 100644
--- a/src/Data/Number.php
+++ b/src/Data/Number.php
@@ -12,17 +12,17 @@
namespace chillerlan\QRCode\Data;
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
/**
- *
+ * Numeric mode: decimal digits 0 through 9
*/
class Number extends QRDataAbstract{
/**
* @var int
*/
- public $mode = self::MODE_NUMBER;
+ protected $datamode = QRCode::DATA_NUMBER;
/**
* @var array
@@ -30,25 +30,23 @@ class Number extends QRDataAbstract{
protected $lengthBits = [10, 12, 14];
/**
- * @param \chillerlan\QRCode\BitBuffer $buffer
- *
- * @return void
+ * @inheritdoc
*/
- public function write(BitBuffer &$buffer){
+ protected function write(string $data){
$i = 0;
- while($i + 2 < $this->dataLength){
- $buffer->put($this->parseInt(substr($this->data, $i, 3)), 10);
+ while($i + 2 < $this->strlen){
+ $this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10);
$i += 3;
}
- if($i < $this->dataLength){
+ if($i < $this->strlen){
- if($this->dataLength - $i === 1){
- $buffer->put($this->parseInt(substr($this->data, $i, $i + 1)), 4);
+ if($this->strlen - $i === 1){
+ $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4);
}
- elseif($this->dataLength - $i === 2){
- $buffer->put($this->parseInt(substr($this->data, $i, $i + 2)), 7);
+ elseif($this->strlen - $i === 2){
+ $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7);
}
}
@@ -61,7 +59,7 @@ class Number extends QRDataAbstract{
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
- private static function parseInt(string $string):int {
+ protected function parseInt(string $string):int {
$num = 0;
$len = strlen($string);
diff --git a/src/Data/QRDataAbstract.php b/src/Data/QRDataAbstract.php
index 42a9f1096..49c5a06ff 100644
--- a/src/Data/QRDataAbstract.php
+++ b/src/Data/QRDataAbstract.php
@@ -12,20 +12,29 @@
namespace chillerlan\QRCode\Data;
+use chillerlan\QRCode\{
+ QRCode, QRCodeException, QROptions
+};
+use chillerlan\QRCode\Helpers\{
+ BitBuffer, Polynomial
+};
+use chillerlan\Traits\ClassLoader;
+
/**
*
*/
abstract class QRDataAbstract implements QRDataInterface{
-
- /**
- * @var string
- */
- public $data;
+ use ClassLoader;
/**
* @var int
*/
- public $dataLength;
+ protected $strlen;
+
+ /**
+ * @var int
+ */
+ protected $datamode;
/**
* @var array
@@ -33,37 +42,298 @@ abstract class QRDataAbstract implements QRDataInterface{
protected $lengthBits = [0, 0, 0];
/**
- * QRDataAbstract constructor.
- *
- * @param string $data
+ * @var int
*/
- public function __construct(string $data){
- $this->data = $data;
- $this->dataLength = strlen($data);
+ protected $version;
+
+ /**
+ * @var array
+ */
+ protected $ecdata;
+
+ /**
+ * @var array
+ */
+ protected $dcdata;
+
+ /**
+ * @var array
+ */
+ protected $matrixdata;
+
+ /**
+ * @var \chillerlan\QRCode\QROptions
+ */
+ protected $options;
+
+ /**
+ * @var \chillerlan\QRCode\Helpers\BitBuffer
+ */
+ protected $bitBuffer;
+
+ /**
+ * QRDataInterface constructor.
+ *
+ * @param \chillerlan\QRCode\QROptions $options
+ * @param string|null $data
+ */
+ public function __construct(QROptions $options, string $data = null){
+ $this->options = $options;
+
+ if($data !== null){
+ $this->setData($data);
+ }
}
/**
- * @see QRCode::createData()
+ * @param string $data
*
- * @param int $type
+ * @return \chillerlan\QRCode\Data\QRDataInterface
+ */
+ public function setData(string $data):QRDataInterface{
+
+ if($this->datamode === QRCode::DATA_KANJI){
+ $data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data));
+ }
+
+ $this->strlen = $this->getLength($data);
+ $this->version = $this->options->version === QRCode::VERSION_AUTO
+ ? $this->getMinimumVersion()
+ : $this->options->version;
+
+ $this->matrixdata = $this
+ ->writeBitBuffer($data)
+ ->maskECC()
+ ;
+
+ return $this;
+ }
+
+ /**
+ * @param int $maskPattern
+ * @param bool $test
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
+ /** @var \chillerlan\QRCode\Data\QRMatrix $matrix */
+ $matrix = $this->loadClass(QRMatrix::class, null, $this->version, $this->options->eccLevel);
+
+ return $matrix
+ ->setFinderPattern()
+ ->setSeparators()
+ ->setAlignmentPattern()
+ ->setTimingPattern()
+ ->setVersionNumber($test)
+ ->setFormatInfo($maskPattern, $test)
+ ->setDarkModule()
+ ->mapData($this->matrixdata, $maskPattern)
+ ;
+ }
+
+ /**
+ * @param int $version
*
* @return int
* @throws \chillerlan\QRCode\Data\QRCodeDataException
* @codeCoverageIgnore
*/
- public function getLengthInBits(int $type):int {
+ protected function getLengthBits(int $version):int {
- switch(true){
- case $type >= 1 && $type <= 9:
- return $this->lengthBits[0]; // 1 - 9
- case $type <= 26:
- return $this->lengthBits[1]; // 10 - 26
- case $type <= 40:
- return $this->lengthBits[2]; // 27 - 40
- default:
- throw new QRCodeDataException('$type: '.$type);
+ foreach([9, 26, 40] as $key => $breakpoint){
+ if($version <= $breakpoint){
+ return $this->lengthBits[$key];
+ }
+ }
+
+ throw new QRCodeDataException('invalid version number: '.$version);
+ }
+
+ /**
+ * returns the byte count of the string
+ *
+ * @param string $data
+ *
+ * @return int
+ */
+ protected function getLength(string $data):int{
+ return strlen($data);
+ }
+
+ /**
+ * @return int
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ protected function getMinimumVersion():int{
+
+ // guess the version number within the given range
+ foreach(range(max(1, $this->options->versionMin), min($this->options->versionMax, 40)) as $version){
+ $maxlength = self::MAX_LENGTH[$version][QRCode::DATA_MODES[$this->datamode]][QRCode::ECC_MODES[$this->options->eccLevel]];
+
+ if($this->strlen <= $maxlength){
+ return $version;
+ }
}
+ throw new QRCodeDataException('data exceeds '.$maxlength.' characters');
+ }
+
+ /**
+ * writes the $data bits to $this->bitBuffer
+ *
+ * @param string $data
+ *
+ * @return void
+ */
+ abstract protected function write(string $data);
+
+ /**
+ * @param string $data
+ *
+ * @return \chillerlan\QRCode\Data\QRDataAbstract
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ protected function writeBitBuffer(string $data):QRDataInterface {
+ $this->bitBuffer = new BitBuffer;
+
+ // @todo: fixme, get real length
+ $MAX_BITS = self::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
+
+ $this->bitBuffer
+ ->clear()
+ ->put($this->datamode, 4)
+ ->put($this->strlen, $this->getLengthBits($this->version))
+ ;
+
+ $this->write($data);
+
+ if($this->bitBuffer->length > $MAX_BITS){
+ throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)');
+ }
+
+ // end code.
+ if($this->bitBuffer->length + 4 <= $MAX_BITS){
+ $this->bitBuffer->put(0, 4);
+ }
+
+ // padding
+ while($this->bitBuffer->length % 8 !== 0){
+ $this->bitBuffer->putBit(false);
+ }
+
+ // padding
+ while(true){
+
+ if($this->bitBuffer->length >= $MAX_BITS){
+ break;
+ }
+
+ $this->bitBuffer->put(0xEC, 8);
+
+ if($this->bitBuffer->length >= $MAX_BITS){
+ break;
+ }
+
+ $this->bitBuffer->put(0x11, 8);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
+ *
+ * @return array
+ */
+ protected function maskECC():array {
+ list($l1, $l2, $b1, $b2) = self::RSBLOCKS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
+
+ $rsBlocks = array_fill(0, $l1, [$b1, $b2]);
+ $rsCount = $l1 + $l2;
+ $this->ecdata = array_fill(0, $rsCount, null);
+ $this->dcdata = $this->ecdata;
+
+ if($l2 > 0){
+ $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
+ }
+
+ $totalCodeCount = 0;
+ $maxDcCount = 0;
+ $maxEcCount = 0;
+ $offset = 0;
+
+ foreach($rsBlocks as $key => $block){
+ list($rsBlockTotal, $dcCount) = $block;
+
+ $ecCount = $rsBlockTotal - $dcCount;
+ $maxDcCount = max($maxDcCount, $dcCount);
+ $maxEcCount = max($maxEcCount, $ecCount);
+ $this->dcdata[$key] = array_fill(0, $dcCount, null);
+
+ foreach($this->dcdata[$key] as $a => $_z){
+ $this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset];
+ }
+
+ list($num, $add) = $this->poly($key, $ecCount);
+
+ foreach($this->ecdata[$key] as $c => $_z){
+ $modIndex = $c + $add;
+ $this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
+ }
+
+ $offset += $dcCount;
+ $totalCodeCount += $rsBlockTotal;
+ }
+
+ $data = array_fill(0, $totalCodeCount, null);
+ $index = 0;
+
+ $mask = function($arr, $count) use (&$data, &$index, $rsCount){
+ for($x = 0; $x < $count; $x++){
+ for($y = 0; $y < $rsCount; $y++){
+ if($x < count($arr[$y])){
+ $data[$index] = $arr[$y][$x];
+ $index++;
+ }
+ }
+ }
+ };
+
+ $mask($this->dcdata, $maxDcCount);
+ $mask($this->ecdata, $maxEcCount);
+
+ return $data;
+ }
+
+ /**
+ * @param int $key
+ * @param int $count
+ *
+ * @return int[]
+ */
+ protected function poly(int $key, int $count):array{
+ $rsPoly = new Polynomial;
+ $modPoly = new Polynomial;
+
+ for($i = 0; $i < $count; $i++){
+ $modPoly->setNum([1, $modPoly->gexp($i)]);
+ $rsPoly->multiply($modPoly->getNum());
+ }
+
+ $rsPolyCount = count($rsPoly->getNum());
+
+ $modPoly
+ ->setNum($this->dcdata[$key], $rsPolyCount - 1)
+ ->mod($rsPoly->getNum())
+ ;
+
+ $this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
+ $num = $modPoly->getNum();
+
+ return [
+ $num,
+ count($num) - count($this->ecdata[$key]),
+ ];
}
}
diff --git a/src/Data/QRDataInterface.php b/src/Data/QRDataInterface.php
index 9063e8453..efc034c35 100644
--- a/src/Data/QRDataInterface.php
+++ b/src/Data/QRDataInterface.php
@@ -12,39 +12,161 @@
namespace chillerlan\QRCode\Data;
-use chillerlan\QRCode\BitBuffer;
-
/**
- * @property string data
- * @property int dataLength
- * @property int mode
+ *
*/
interface QRDataInterface{
- const MODE_NUMBER = 1 << 0;
- const MODE_ALPHANUM = 1 << 1;
- const MODE_BYTE = 1 << 2;
- const MODE_KANJI = 1 << 3;
+ /**
+ * @link http://www.qrcode.com/en/about/version.html
+ */
+ const MAX_LENGTH =[ 1 => // start at 1
+ // [NUMERIC => [L, M, Q, H ], ALPHANUM => [L, M, Q, H], BINARY => [L, M, Q, H ], KANJI => [L, M, Q, H ]] // modules
+ [[ 41, 34, 27, 17], [ 25, 20, 16, 10], [ 17, 14, 11, 7], [ 10, 8, 7, 4]], // 21
+ [[ 77, 63, 48, 34], [ 47, 38, 29, 20], [ 32, 26, 20, 14], [ 20, 16, 12, 8]], // 25
+ [[ 127, 101, 77, 58], [ 77, 61, 47, 35], [ 53, 42, 32, 24], [ 32, 26, 20, 15]], // 29
+ [[ 187, 149, 111, 82], [ 114, 90, 67, 50], [ 78, 62, 46, 34], [ 48, 38, 28, 21]], // 33
+ [[ 255, 202, 144, 106], [ 154, 122, 87, 64], [ 106, 84, 60, 44], [ 65, 52, 37, 27]], // 37
+ [[ 322, 255, 178, 139], [ 195, 154, 108, 84], [ 134, 106, 74, 58], [ 82, 65, 45, 36]], // 41
+ [[ 370, 293, 207, 154], [ 224, 178, 125, 93], [ 154, 122, 86, 64], [ 95, 75, 53, 39]], // 45
+ [[ 461, 365, 259, 202], [ 279, 221, 157, 122], [ 192, 152, 108, 84], [ 118, 93, 66, 52]], // 49
+ [[ 552, 432, 312, 235], [ 335, 262, 189, 143], [ 230, 180, 130, 98], [ 141, 111, 80, 60]], // 53
+ [[ 652, 513, 364, 288], [ 395, 311, 221, 174], [ 271, 213, 151, 119], [ 167, 131, 93, 74]], // 57
+ [[ 772, 604, 427, 331], [ 468, 366, 259, 200], [ 321, 251, 177, 137], [ 198, 155, 109, 85]], // 61
+ [[ 883, 691, 489, 374], [ 535, 419, 296, 227], [ 367, 287, 203, 155], [ 226, 177, 125, 96]], // 65
+ [[1022, 796, 580, 427], [ 619, 483, 352, 259], [ 425, 331, 241, 177], [ 262, 204, 149, 109]], // 69 NICE!
+ [[1101, 871, 621, 468], [ 667, 528, 376, 283], [ 458, 362, 258, 194], [ 282, 223, 159, 120]], // 73
+ [[1250, 991, 703, 530], [ 758, 600, 426, 321], [ 520, 412, 292, 220], [ 320, 254, 180, 136]], // 77
+ [[1408, 1082, 775, 602], [ 854, 656, 470, 365], [ 586, 450, 322, 250], [ 361, 277, 198, 154]], // 81
+ [[1548, 1212, 876, 674], [ 938, 734, 531, 408], [ 644, 504, 364, 280], [ 397, 310, 224, 173]], // 85
+ [[1725, 1346, 948, 746], [1046, 816, 574, 452], [ 718, 560, 394, 310], [ 442, 345, 243, 191]], // 89
+ [[1903, 1500, 1063, 813], [1153, 909, 644, 493], [ 792, 624, 442, 338], [ 488, 384, 272, 208]], // 93
+ [[2061, 1600, 1159, 919], [1249, 970, 702, 557], [ 858, 666, 482, 382], [ 528, 410, 297, 235]], // 97
+ [[2232, 1708, 1224, 969], [1352, 1035, 742, 587], [ 929, 711, 509, 403], [ 572, 438, 314, 248]], // 101
+ [[2409, 1872, 1358, 1056], [1460, 1134, 823, 640], [1003, 779, 565, 439], [ 618, 480, 348, 270]], // 105
+ [[2620, 2059, 1468, 1108], [1588, 1248, 890, 672], [1091, 857, 611, 461], [ 672, 528, 376, 284]], // 109
+ [[2812, 2188, 1588, 1228], [1704, 1326, 963, 744], [1171, 911, 661, 511], [ 721, 561, 407, 315]], // 113
+ [[3057, 2395, 1718, 1286], [1853, 1451, 1041, 779], [1273, 997, 715, 535], [ 784, 614, 440, 330]], // 117
+ [[3283, 2544, 1804, 1425], [1990, 1542, 1094, 864], [1367, 1059, 751, 593], [ 842, 652, 462, 365]], // 121
+ [[3517, 2701, 1933, 1501], [2132, 1637, 1172, 910], [1465, 1125, 805, 625], [ 902, 692, 496, 385]], // 125
+ [[3669, 2857, 2085, 1581], [2223, 1732, 1263, 958], [1528, 1190, 868, 658], [ 940, 732, 534, 405]], // 129
+ [[3909, 3035, 2181, 1677], [2369, 1839, 1322, 1016], [1628, 1264, 908, 698], [1002, 778, 559, 430]], // 133
+ [[4158, 3289, 2358, 1782], [2520, 1994, 1429, 1080], [1732, 1370, 982, 742], [1066, 843, 604, 457]], // 137
+ [[4417, 3486, 2473, 1897], [2677, 2113, 1499, 1150], [1840, 1452, 1030, 790], [1132, 894, 634, 486]], // 141
+ [[4686, 3693, 2670, 2022], [2840, 2238, 1618, 1226], [1952, 1538, 1112, 842], [1201, 947, 684, 518]], // 145
+ [[4965, 3909, 2805, 2157], [3009, 2369, 1700, 1307], [2068, 1628, 1168, 898], [1273, 1002, 719, 553]], // 149
+ [[5253, 4134, 2949, 2301], [3183, 2506, 1787, 1394], [2188, 1722, 1228, 958], [1347, 1060, 756, 590]], // 153
+ [[5529, 4343, 3081, 2361], [3351, 2632, 1867, 1431], [2303, 1809, 1283, 983], [1417, 1113, 790, 605]], // 157
+ [[5836, 4588, 3244, 2524], [3537, 2780, 1966, 1530], [2431, 1911, 1351, 1051], [1496, 1176, 832, 647]], // 161
+ [[6153, 4775, 3417, 2625], [3729, 2894, 2071, 1591], [2563, 1989, 1423, 1093], [1577, 1224, 876, 673]], // 165
+ [[6479, 5039, 3599, 2735], [3927, 3054, 2181, 1658], [2699, 2099, 1499, 1139], [1661, 1292, 923, 701]], // 169
+ [[6743, 5313, 3791, 2927], [4087, 3220, 2298, 1774], [2809, 2213, 1579, 1219], [1729, 1362, 972, 750]], // 173
+ [[7089, 5596, 3993, 3057], [4296, 3391, 2420, 1852], [2953, 2331, 1663, 1273], [1817, 1435, 1024, 784]], // 177
+ ];
- const MODE = [
- self::MODE_NUMBER => 0,
- self::MODE_ALPHANUM => 1,
- self::MODE_BYTE => 2,
- self::MODE_KANJI => 3,
+ const MAX_BITS = [ 1 => // start at 1
+ // MAX_BITS => [L, M, Q, H ]
+ [ 152, 128, 104, 72],
+ [ 272, 224, 176, 128],
+ [ 440, 352, 272, 208],
+ [ 640, 512, 384, 288],
+ [ 864, 688, 496, 368],
+ [ 1088, 864, 608, 480],
+ [ 1248, 992, 704, 528],
+ [ 1552, 1232, 880, 688],
+ [ 1856, 1456, 1056, 800],
+ [ 2192, 1728, 1232, 976],
+ [ 2592, 2032, 1440, 1120],
+ [ 2960, 2320, 1648, 1264],
+ [ 3424, 2672, 1952, 1440],
+ [ 3688, 2920, 2088, 1576],
+ [ 4184, 3320, 2360, 1784],
+ [ 4712, 3624, 2600, 2024],
+ [ 5176, 4056, 2936, 2264],
+ [ 5768, 4504, 3176, 2504],
+ [ 6360, 5016, 3560, 2728],
+ [ 6888, 5352, 3880, 3080],
+ [ 7456, 5712, 4096, 3248],
+ [ 8048, 6256, 4544, 3536],
+ [ 8752, 6880, 4912, 3712],
+ [ 9392, 7312, 5312, 4112],
+ [10208, 8000, 5744, 4304],
+ [10960, 8496, 6032, 4768],
+ [11744, 9024, 6464, 5024],
+ [12248, 9544, 6968, 5288],
+ [13048, 10136, 7288, 5608],
+ [13880, 10984, 7880, 5960],
+ [14744, 11640, 8264, 6344],
+ [15640, 12328, 8920, 6760],
+ [16568, 13048, 7208, 9368],
+ [17528, 13800, 9848, 7688],
+ [18448, 14496, 10288, 7888],
+ [19472, 15312, 10832, 8432],
+ [20528, 15936, 11408, 8768],
+ [21616, 16816, 12016, 9136],
+ [22496, 17728, 12656, 9776],
+ [23648, 18672, 13328, 10208],
];
/**
- * @param \chillerlan\QRCode\BitBuffer $buffer
- * @return void
+ * @link http://www.thonky.com/qr-code-tutorial/error-correction-table
*/
- public function write(BitBuffer &$buffer);
+ const RSBLOCKS = [ 1 => // start at 1
+ [[ 1, 0, 26, 19], [ 1, 0, 26, 16], [ 1, 0, 26, 13], [ 1, 0, 26, 9]], // 1
+ [[ 1, 0, 44, 34], [ 1, 0, 44, 28], [ 1, 0, 44, 22], [ 1, 0, 44, 16]], //
+ [[ 1, 0, 70, 55], [ 1, 0, 70, 44], [ 2, 0, 35, 17], [ 2, 0, 35, 13]], //
+ [[ 1, 0, 100, 80], [ 2, 0, 50, 32], [ 2, 0, 50, 24], [ 4, 0, 25, 9]], //
+ [[ 1, 0, 134, 108], [ 2, 0, 67, 43], [ 2, 2, 33, 15], [ 2, 2, 33, 11]], // 5
+ [[ 2, 0, 86, 68], [ 4, 0, 43, 27], [ 4, 0, 43, 19], [ 4, 0, 43, 15]], //
+ [[ 2, 0, 98, 78], [ 4, 0, 49, 31], [ 2, 4, 32, 14], [ 4, 1, 39, 13]], //
+ [[ 2, 0, 121, 97], [ 2, 2, 60, 38], [ 4, 2, 40, 18], [ 4, 2, 40, 14]], //
+ [[ 2, 0, 146, 116], [ 3, 2, 58, 36], [ 4, 4, 36, 16], [ 4, 4, 36, 12]], //
+ [[ 2, 2, 86, 68], [ 4, 1, 69, 43], [ 6, 2, 43, 19], [ 6, 2, 43, 15]], // 10
+ [[ 4, 0, 101, 81], [ 1, 4, 80, 50], [ 4, 4, 50, 22], [ 3, 8, 36, 12]], //
+ [[ 2, 2, 116, 92], [ 6, 2, 58, 36], [ 4, 6, 46, 20], [ 7, 4, 42, 14]], //
+ [[ 4, 0, 133, 107], [ 8, 1, 59, 37], [ 8, 4, 44, 20], [12, 4, 33, 11]], //
+ [[ 3, 1, 145, 115], [ 4, 5, 64, 40], [11, 5, 36, 16], [11, 5, 36, 12]], //
+ [[ 5, 1, 109, 87], [ 5, 5, 65, 41], [ 5, 7, 54, 24], [11, 7, 36, 12]], // 15
+ [[ 5, 1, 122, 98], [ 7, 3, 73, 45], [15, 2, 43, 19], [ 3, 13, 45, 15]], //
+ [[ 1, 5, 135, 107], [10, 1, 74, 46], [ 1, 15, 50, 22], [ 2, 17, 42, 14]], //
+ [[ 5, 1, 150, 120], [ 9, 4, 69, 43], [17, 1, 50, 22], [ 2, 19, 42, 14]], //
+ [[ 3, 4, 141, 113], [ 3, 11, 70, 44], [17, 4, 47, 21], [ 9, 16, 39, 13]], //
+ [[ 3, 5, 135, 107], [ 3, 13, 67, 41], [15, 5, 54, 24], [15, 10, 43, 15]], // 20
+ [[ 4, 4, 144, 116], [17, 0, 68, 42], [17, 6, 50, 22], [19, 6, 46, 16]], //
+ [[ 2, 7, 139, 111], [17, 0, 74, 46], [ 7, 16, 54, 24], [34, 0, 37, 13]], //
+ [[ 4, 5, 151, 121], [ 4, 14, 75, 47], [11, 14, 54, 24], [16, 14, 45, 15]], //
+ [[ 6, 4, 147, 117], [ 6, 14, 73, 45], [11, 16, 54, 24], [30, 2, 46, 16]], //
+ [[ 8, 4, 132, 106], [ 8, 13, 75, 47], [ 7, 22, 54, 24], [22, 13, 45, 15]], // 25
+ [[10, 2, 142, 114], [19, 4, 74, 46], [28, 6, 50, 22], [33, 4, 46, 16]], //
+ [[ 8, 4, 152, 122], [22, 3, 73, 45], [ 8, 26, 53, 23], [12, 28, 45, 15]], //
+ [[ 3, 10, 147, 117], [ 3, 23, 73, 45], [ 4, 31, 54, 24], [11, 31, 45, 15]], //
+ [[ 7, 7, 146, 116], [21, 7, 73, 45], [ 1, 37, 53, 23], [19, 26, 45, 15]], //
+ [[ 5, 10, 145, 115], [19, 10, 75, 47], [15, 25, 54, 24], [23, 25, 45, 15]], // 30
+ [[13, 3, 145, 115], [ 2, 29, 74, 46], [42, 1, 54, 24], [23, 28, 45, 15]], //
+ [[17, 0, 145, 115], [10, 23, 74, 46], [10, 35, 54, 24], [19, 35, 45, 15]], //
+ [[17, 1, 145, 115], [14, 21, 74, 46], [29, 19, 54, 24], [11, 46, 45, 15]], //
+ [[13, 6, 145, 115], [14, 23, 74, 46], [44, 7, 54, 24], [59, 1, 46, 16]], //
+ [[12, 7, 151, 121], [12, 26, 75, 47], [39, 14, 54, 24], [22, 41, 45, 15]], // 35
+ [[ 6, 14, 151, 121], [ 6, 34, 75, 47], [46, 10, 54, 24], [ 2, 64, 45, 15]], //
+ [[17, 4, 152, 122], [29, 14, 74, 46], [49, 10, 54, 24], [24, 46, 45, 15]], //
+ [[ 4, 18, 152, 122], [13, 32, 74, 46], [48, 14, 54, 24], [42, 32, 45, 15]], //
+ [[20, 4, 147, 117], [40, 7, 75, 47], [43, 22, 54, 24], [10, 67, 45, 15]], //
+ [[19, 6, 148, 118], [18, 31, 75, 47], [34, 34, 54, 24], [20, 61, 45, 15]], // 40
+ ];
/**
- * @param $type
+ * @param string $data
*
- * @return int
- * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ * @return \chillerlan\QRCode\Data\QRDataInterface
*/
- public function getLengthInBits(int $type):int;
+ public function setData(string $data);
+
+ /**
+ * @param int $maskPattern
+ * @param bool $test
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function initMatrix(int $maskPattern, bool $test = null):QRMatrix;
}
diff --git a/src/Data/QRMatrix.php b/src/Data/QRMatrix.php
new file mode 100644
index 000000000..104a8afdf
--- /dev/null
+++ b/src/Data/QRMatrix.php
@@ -0,0 +1,563 @@
+
+ * @copyright 2017 Smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Data;
+
+use chillerlan\QRCode\QRCode;
+
+/**
+ * @link http://www.thonky.com/qr-code-tutorial/format-version-information
+ */
+class QRMatrix{
+
+ const M_NULL = 0x00;
+ const M_DARKMODULE = 0x02;
+ const M_DATA = 0x04;
+ const M_FINDER = 0x06;
+ const M_SEPARATOR = 0x08;
+ const M_ALIGNMENT = 0x0a;
+ const M_TIMING = 0x0c;
+ const M_FORMAT = 0x0e;
+ const M_VERSION = 0x10;
+ const M_QUIETZONE = 0x12;
+ const M_LOGO = 0x14; // @todo
+
+ const M_TEST = 0xff;
+
+ /**
+ * @link http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations
+ */
+ const alignmentPattern = [ 1 => // start at 1
+ [],
+ [6, 18],
+ [6, 22],
+ [6, 26],
+ [6, 30],
+ [6, 34],
+ [6, 22, 38],
+ [6, 24, 42],
+ [6, 26, 46],
+ [6, 28, 50],
+ [6, 30, 54],
+ [6, 32, 58],
+ [6, 34, 62],
+ [6, 26, 46, 66],
+ [6, 26, 48, 70],
+ [6, 26, 50, 74],
+ [6, 30, 54, 78],
+ [6, 30, 56, 82],
+ [6, 30, 58, 86],
+ [6, 34, 62, 90],
+ [6, 28, 50, 72, 94],
+ [6, 26, 50, 74, 98],
+ [6, 30, 54, 78, 102],
+ [6, 28, 54, 80, 106],
+ [6, 32, 58, 84, 110],
+ [6, 30, 58, 86, 114],
+ [6, 34, 62, 90, 118],
+ [6, 26, 50, 74, 98, 122],
+ [6, 30, 54, 78, 102, 126],
+ [6, 26, 52, 78, 104, 130],
+ [6, 30, 56, 82, 108, 134],
+ [6, 34, 60, 86, 112, 138],
+ [6, 30, 58, 86, 114, 142],
+ [6, 34, 62, 90, 118, 146],
+ [6, 30, 54, 78, 102, 126, 150],
+ [6, 24, 50, 76, 102, 128, 154],
+ [6, 28, 54, 80, 106, 132, 158],
+ [6, 32, 58, 84, 110, 136, 162],
+ [6, 26, 54, 82, 110, 138, 166],
+ [6, 30, 58, 86, 114, 142, 170],
+ ];
+
+ /**
+ * @link http://www.thonky.com/qr-code-tutorial/format-version-tables
+ */
+ const versionPattern = [ 7 => // no version pattern for QR Codes < 7
+ 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, // 7-10
+ 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,
+ ];
+
+ const formatPattern = [
+ [0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976], // L
+ [0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0], // M
+ [0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed], // Q
+ [0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b], // H
+ ];
+
+ /**
+ * @var int
+ */
+ protected $version;
+
+ /**
+ * @var int
+ */
+ protected $eclevel;
+
+ /**
+ * @var int
+ */
+ protected $maskPattern = -1;
+
+ /**
+ * @var int
+ */
+ protected $moduleCount;
+
+ /**
+ * @var mixed[]
+ */
+ protected $matrix;
+
+ /**
+ * QRMatrix constructor.
+ *
+ * @param int $version
+ * @param int $eclevel
+ *
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ public function __construct(int $version, int $eclevel){
+
+ if(!in_array($version, range(1, 40), true)){
+ throw new QRCodeDataException('invalid QR Code version');
+ }
+
+ if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
+ throw new QRCodeDataException('invalid ecc level');
+ }
+
+ $this->version = $version;
+ $this->eclevel = $eclevel;
+ $this->moduleCount = $this->version * 4 + 17;
+ $this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, self::M_NULL));
+ }
+
+ /**
+ * Returns the data array
+ *
+ * @return array
+ */
+ public function matrix():array {
+ return $this->matrix;
+ }
+
+ /**
+ * @return int
+ */
+ public function version():int {
+ return $this->version;
+ }
+
+ /**
+ * @return int
+ */
+ public function eccLevel():int {
+ return $this->eclevel;
+ }
+
+ /**
+ * @return int
+ */
+ public function maskPattern():int {
+ return $this->maskPattern;
+ }
+
+ /**
+ * Returns the absoulute size of the matrix, including quiet zone (after setting it).
+ *
+ * size = version * 4 + 17 [ + 2 * quietzone size]
+ *
+ * @return int
+ */
+ public function size():int{
+ return $this->moduleCount;
+ }
+
+ /**
+ * Returns the value of the module at position [$x, $y]
+ *
+ * @param int $x
+ * @param int $y
+ *
+ * @return int
+ */
+ public function get(int $x, int $y):int{
+ return $this->matrix[$y][$x];
+ }
+
+ /**
+ * Sets the $M_TYPE value for the module at position [$x, $y]
+ *
+ * true => $M_TYPE << 8
+ * false => $M_TYPE
+ *
+ * @param int $x
+ * @param int $y
+ * @param int $M_TYPE
+ * @param bool $value
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{
+ $this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0);
+
+ return $this;
+ }
+
+ /**
+ * Checks whether a module is true (dark) or false (light)
+ *
+ * true => $value >> 8 === $M_TYPE
+ * $value >> 8 > 0
+ *
+ * false => $value === $M_TYPE
+ * $value >> 8 === 0
+ *
+ * @param int $x
+ * @param int $y
+ *
+ * @return bool
+ */
+ public function check(int $x, int $y):bool{
+ return $this->matrix[$y][$x] >> 8 > 0;
+ }
+
+
+ /**
+ * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setDarkModule():QRMatrix{
+ $this->set(8, 4 * $this->version + 9, true, self::M_DARKMODULE);
+
+ return $this;
+ }
+
+ /**
+ * Draws the 7x7 finder patterns in the corners top left/right and bottom left
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setFinderPattern():QRMatrix{
+
+ $pos = [
+ [0, 0], // top left
+ [$this->moduleCount - 7, 0], // bottom left
+ [0, $this->moduleCount - 7], // top right
+ ];
+
+ foreach($pos as $c){
+ for($y = 0; $y < 7; $y++){
+ for($x = 0; $x < 7; $x++){
+ $this->set(
+ $c[0] + $y,
+ $c[1] + $x,
+ !(($x > 0 && $x < 6 && ($y === 1 || $y === 5)) || ($y > 0 && $y < 6 && ($x === 1 || $x === 5))),
+ self::M_FINDER
+ );
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Draws the separator lines around the finder patterns
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setSeparators():QRMatrix{
+
+ $h = [
+ [7, 0],
+ [$this->moduleCount - 8, 0],
+ [7, $this->moduleCount - 8],
+ ];
+
+ $v = [
+ [7, 7],
+ [$this->moduleCount - 1, 7],
+ [7, $this->moduleCount - 8],
+ ];
+
+ $t = self::M_SEPARATOR;
+
+ for($c = 0; $c < 3; $c++){
+ for($i = 0; $i < 8; $i++){
+ $this->set($h[$c][0] , $h[$c][1] + $i, false, $t);
+ $this->set($v[$c][0] - $i, $v[$c][1] , false, $t);
+ }
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Draws the 5x5 alignment patterns
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setAlignmentPattern():QRMatrix{
+ $pattern = self::alignmentPattern[$this->version];
+
+ foreach($pattern as $y){
+ foreach($pattern as $x){
+
+ // skip existing patterns
+ if($this->matrix[$y][$x] !== self::M_NULL){
+ continue;
+ }
+
+ for($ry = -2; $ry <= 2; $ry++){
+ for($rx = -2; $rx <= 2; $rx++){
+ $v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2;
+
+ $this->set($x + $rx, $y + $ry, $v, self::M_ALIGNMENT);
+ }
+ }
+
+ }
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Draws the timing pattern (h/v checkered line between the finder patterns)
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setTimingPattern():QRMatrix{
+
+ foreach(range(8, $this->moduleCount - 8 - 1) as $i){
+
+ if($this->matrix[6][$i] !== self::M_NULL || $this->matrix[$i][6] !== self::M_NULL){
+ continue;
+ }
+
+ $v = $i % 2 === 0;
+ $t = self::M_TIMING;
+
+ $this->set($i, 6, $v, $t); // h
+ $this->set(6, $i, $v, $t); // v
+ }
+
+ return $this;
+ }
+
+ /**
+ * Draws the version information, 2x 3x6 pixel
+ *
+ * @param bool|null $test
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setVersionNumber(bool $test = null):QRMatrix{
+ $test = $test !== null ? $test : false;
+
+ $bits = self::versionPattern[$this->version] ?? false;
+
+ if($bits !== false){
+
+ for($i = 0; $i < 18; $i++){
+ $a = (int)floor($i / 3);
+ $b = $i % 3 + $this->moduleCount - 8 - 3;
+ $v = !$test && (($bits >> $i) & 1) === 1;
+ $t = self::M_VERSION;
+
+ $this->set($b, $a, $v, $t); // ne
+ $this->set($a, $b, $v, $t); // sw
+ }
+
+ }
+
+ return $this;
+ }
+
+ /**
+ * Draws the format info along the finder patterns
+ *
+ * @param int $maskPattern
+ * @param bool|null $test
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{
+ $test = $test !== null ? $test : false;
+ $bits = self::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0;
+ $t = self::M_FORMAT;
+
+ for($i = 0; $i < 15; $i++){
+ $v = !$test && (($bits >> $i) & 1) === 1;
+
+ if($i < 6){
+ $this->set(8, $i, $v, $t);
+ }
+ elseif($i < 8){
+ $this->set(8, $i + 1, $v, $t);
+ }
+ else{
+ $this->set(8, $this->moduleCount - 15 + $i, $v, $t);
+ }
+
+ if($i < 8){
+ $this->set($this->moduleCount - $i - 1, 8, $v, $t);
+ }
+ elseif($i < 9){
+ $this->set(15 - $i, 8, $v, $t);
+ }
+ else{
+ $this->set(15 - $i - 1, 8, $v, $t);
+ }
+
+ }
+
+ $this->set(8, $this->moduleCount - 8, !$test, $t);
+
+ return $this;
+ }
+
+ /**
+ * Draws the "quiet zone" of $size around the matrix
+ *
+ * @param int|null $size
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function setQuietZone(int $size = null):QRMatrix{
+
+ if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === self::M_NULL){
+ throw new QRCodeDataException('use only after writing data');
+ }
+
+ $size = $size !== null ? max(0, min($size, floor($this->moduleCount / 2))) : 4;
+ $t = self::M_QUIETZONE;
+
+ for($y = 0; $y < $this->moduleCount; $y++){
+ for($i = 0; $i < $size; $i++){
+ array_unshift($this->matrix[$y], $t);
+ array_push($this->matrix[$y], $t);
+ }
+ }
+
+ $this->moduleCount += ($size * 2);
+ $r = array_fill(0, $this->moduleCount, $t);
+
+ for($i = 0; $i < $size; $i++){
+ array_unshift($this->matrix, $r);
+ array_push($this->matrix, $r);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Maps the binary $data array from QRDataInterface::maskECC() on the matrix, using $maskPattern
+ *
+ * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
+ *
+ * @param int[] $data
+ * @param int $maskPattern
+ *
+ * @return \chillerlan\QRCode\Data\QRMatrix
+ */
+ public function mapData(array $data, int $maskPattern):QRMatrix{
+ $this->maskPattern = $maskPattern;
+ $byteCount = count($data);
+ $size = $this->moduleCount - 1;
+
+ for($i = $size, $y = $size, $inc = -1, $byteIndex = 0, $bitIndex = 7; $i > 0; $i -= 2){
+
+ if($i === 6){
+ $i--;
+ }
+
+ while(true){
+ for($c = 0; $c < 2; $c++){
+ $x = $i - $c;
+
+ if($this->matrix[$y][$x] === self::M_NULL){
+ $v = false;
+
+ if($byteIndex < $byteCount){
+ $v = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
+ }
+
+ if($this->getMask($x, $y, $maskPattern) === 0){
+ $v = !$v;
+ }
+
+ $this->matrix[$y][$x] = self::M_DATA << ($v ? 8 : 0);
+ $bitIndex--;
+
+ if($bitIndex === -1){
+ $byteIndex++;
+ $bitIndex = 7;
+ }
+
+ }
+ }
+
+ $y += $inc;
+
+ if($y < 0 || $this->moduleCount <= $y){
+ $y -= $inc;
+ $inc = -$inc;
+
+ break;
+ }
+
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @see \chillerlan\QRCode\QRMatrix::mapData()
+ *
+ * @internal
+ *
+ * @param int $x
+ * @param int $y
+ * @param int $maskPattern
+ *
+ * @return int
+ */
+ protected function getMask(int $x, int $y, int $maskPattern):int {
+ $a = $y + $x;
+ $m = $y * $x;
+
+ // i hate it so much
+ switch($maskPattern){
+ case 0: return $a % 2;
+ case 1: return $y % 2;
+ case 2: return $x % 3;
+ case 3: return $a % 3;
+ case 4: return (floor($y / 2) + floor($x / 3)) % 2;
+ case 5: return $m % 2 + $m % 3;
+ case 6: return ($m % 2 + $m % 3) % 2;
+ case 7: return ($m % 3 + $a % 2) % 2;
+ }
+
+ throw new QRCodeDataException('invalid mask pattern');
+ }
+
+}
diff --git a/src/BitBuffer.php b/src/Helpers/BitBuffer.php
similarity index 74%
rename from src/BitBuffer.php
rename to src/Helpers/BitBuffer.php
index 813ad46d7..62516a835 100644
--- a/src/BitBuffer.php
+++ b/src/Helpers/BitBuffer.php
@@ -4,21 +4,22 @@
*
* @filesource BitBuffer.php
* @created 25.11.2015
- * @package chillerlan\QRCode
+ * @package chillerlan\QRCode\Data
* @author Smiley
* @copyright 2015 Smiley
* @license MIT
*/
-namespace chillerlan\QRCode;
+namespace chillerlan\QRCode\Helpers;
/**
- *
+ * @property int[] $buffer
+ * @property int $length
*/
class BitBuffer{
/**
- * @var array
+ * @var int[]
*/
public $buffer = [];
@@ -28,7 +29,7 @@ class BitBuffer{
public $length = 0;
/**
- * @return \chillerlan\QRCode\BitBuffer
+ * @return \chillerlan\QRCode\Helpers\BitBuffer
*/
public function clear():BitBuffer{
$this->buffer = [];
@@ -41,7 +42,7 @@ class BitBuffer{
* @param int $num
* @param int $length
*
- * @return \chillerlan\QRCode\BitBuffer
+ * @return \chillerlan\QRCode\Helpers\BitBuffer
*/
public function put(int $num, int $length):BitBuffer{
@@ -55,7 +56,7 @@ class BitBuffer{
/**
* @param bool $bit
*
- * @return \chillerlan\QRCode\BitBuffer
+ * @return \chillerlan\QRCode\Helpers\BitBuffer
*/
public function putBit(bool $bit):BitBuffer{
$bufIndex = floor($this->length / 8);
@@ -64,7 +65,7 @@ class BitBuffer{
$this->buffer[] = 0;
}
- if($bit){
+ if($bit === true){
$this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8));
}
diff --git a/src/Helpers/Polynomial.php b/src/Helpers/Polynomial.php
new file mode 100644
index 000000000..ac86f3421
--- /dev/null
+++ b/src/Helpers/Polynomial.php
@@ -0,0 +1,182 @@
+
+ * @copyright 2015 Smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Helpers;
+
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
+ */
+class Polynomial{
+
+ /**
+ * @link http://www.thonky.com/qr-code-tutorial/log-antilog-table
+ */
+ const table = [
+ [ 1, 0], [ 2, 0], [ 4, 1], [ 8, 25], [ 16, 2], [ 32, 50], [ 64, 26], [128, 198],
+ [ 29, 3], [ 58, 223], [116, 51], [232, 238], [205, 27], [135, 104], [ 19, 199], [ 38, 75],
+ [ 76, 4], [152, 100], [ 45, 224], [ 90, 14], [180, 52], [117, 141], [234, 239], [201, 129],
+ [143, 28], [ 3, 193], [ 6, 105], [ 12, 248], [ 24, 200], [ 48, 8], [ 96, 76], [192, 113],
+ [157, 5], [ 39, 138], [ 78, 101], [156, 47], [ 37, 225], [ 74, 36], [148, 15], [ 53, 33],
+ [106, 53], [212, 147], [181, 142], [119, 218], [238, 240], [193, 18], [159, 130], [ 35, 69],
+ [ 70, 29], [140, 181], [ 5, 194], [ 10, 125], [ 20, 106], [ 40, 39], [ 80, 249], [160, 185],
+ [ 93, 201], [186, 154], [105, 9], [210, 120], [185, 77], [111, 228], [222, 114], [161, 166],
+ [ 95, 6], [190, 191], [ 97, 139], [194, 98], [153, 102], [ 47, 221], [ 94, 48], [188, 253],
+ [101, 226], [202, 152], [137, 37], [ 15, 179], [ 30, 16], [ 60, 145], [120, 34], [240, 136],
+ [253, 54], [231, 208], [211, 148], [187, 206], [107, 143], [214, 150], [177, 219], [127, 189],
+ [254, 241], [225, 210], [223, 19], [163, 92], [ 91, 131], [182, 56], [113, 70], [226, 64],
+ [217, 30], [175, 66], [ 67, 182], [134, 163], [ 17, 195], [ 34, 72], [ 68, 126], [136, 110],
+ [ 13, 107], [ 26, 58], [ 52, 40], [104, 84], [208, 250], [189, 133], [103, 186], [206, 61],
+ [129, 202], [ 31, 94], [ 62, 155], [124, 159], [248, 10], [237, 21], [199, 121], [147, 43],
+ [ 59, 78], [118, 212], [236, 229], [197, 172], [151, 115], [ 51, 243], [102, 167], [204, 87],
+ [133, 7], [ 23, 112], [ 46, 192], [ 92, 247], [184, 140], [109, 128], [218, 99], [169, 13],
+ [ 79, 103], [158, 74], [ 33, 222], [ 66, 237], [132, 49], [ 21, 197], [ 42, 254], [ 84, 24],
+ [168, 227], [ 77, 165], [154, 153], [ 41, 119], [ 82, 38], [164, 184], [ 85, 180], [170, 124],
+ [ 73, 17], [146, 68], [ 57, 146], [114, 217], [228, 35], [213, 32], [183, 137], [115, 46],
+ [230, 55], [209, 63], [191, 209], [ 99, 91], [198, 149], [145, 188], [ 63, 207], [126, 205],
+ [252, 144], [229, 135], [215, 151], [179, 178], [123, 220], [246, 252], [241, 190], [255, 97],
+ [227, 242], [219, 86], [171, 211], [ 75, 171], [150, 20], [ 49, 42], [ 98, 93], [196, 158],
+ [149, 132], [ 55, 60], [110, 57], [220, 83], [165, 71], [ 87, 109], [174, 65], [ 65, 162],
+ [130, 31], [ 25, 45], [ 50, 67], [100, 216], [200, 183], [141, 123], [ 7, 164], [ 14, 118],
+ [ 28, 196], [ 56, 23], [112, 73], [224, 236], [221, 127], [167, 12], [ 83, 111], [166, 246],
+ [ 81, 108], [162, 161], [ 89, 59], [178, 82], [121, 41], [242, 157], [249, 85], [239, 170],
+ [195, 251], [155, 96], [ 43, 134], [ 86, 177], [172, 187], [ 69, 204], [138, 62], [ 9, 90],
+ [ 18, 203], [ 36, 89], [ 72, 95], [144, 176], [ 61, 156], [122, 169], [244, 160], [245, 81],
+ [247, 11], [243, 245], [251, 22], [235, 235], [203, 122], [139, 117], [ 11, 44], [ 22, 215],
+ [ 44, 79], [ 88, 174], [176, 213], [125, 233], [250, 230], [233, 231], [207, 173], [131, 232],
+ [ 27, 116], [ 54, 214], [108, 244], [216, 234], [173, 168], [ 71, 80], [142, 88], [ 1, 175],
+ ];
+
+ /**
+ * @var array
+ */
+ protected $num = [];
+
+ /**
+ * Polynomial constructor.
+ *
+ * @param array $num
+ * @param int $shift
+ */
+ public function __construct(array $num = [1], int $shift = 0){
+ $this->setNum($num, $shift);
+ }
+
+ /**
+ * @return array
+ */
+ public function getNum():array {
+ return $this->num;
+ }
+
+ /**
+ * @param array $num
+ * @param int $shift
+ *
+ * @return \chillerlan\QRCode\Helpers\Polynomial
+ */
+ public function setNum(array $num, int $shift = 0):Polynomial {
+ $offset = 0;
+ $numCount = count($num);
+
+ while($offset < $numCount && $num[$offset] === 0){
+ $offset++;
+ }
+
+ $this->num = array_fill(0, $numCount - $offset + $shift, 0);
+
+ for($i = 0; $i < $numCount - $offset; $i++){
+ $this->num[$i] = $num[$i + $offset];
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $e
+ *
+ * @return \chillerlan\QRCode\Helpers\Polynomial
+ */
+ public function multiply(array $e):Polynomial {
+ $n = array_fill(0, count($this->num) + count($e) - 1, 0);
+
+ foreach($this->num as $i => $vi){
+ $vi = $this->glog($vi);
+
+ foreach($e as $j => $vj){
+ $n[$i + $j] ^= $this->gexp($vi + $this->glog($vj));
+ }
+
+ }
+
+ $this->setNum($n);
+
+ return $this;
+ }
+
+ /**
+ * @param array $e
+ *
+ * @return \chillerlan\QRCode\Helpers\Polynomial
+ */
+ public function mod(array $e):Polynomial{
+ $n = $this->num;
+
+ if(count($n) - count($e) < 0){
+ return $this;
+ }
+
+ $ratio = $this->glog($n[0]) - $this->glog($e[0]);
+
+ foreach($e as $i => $v){
+ $n[$i] ^= $this->gexp($this->glog($v) + $ratio);
+ }
+
+ $this->setNum($n)->mod($e);
+
+ return $this;
+ }
+
+ /**
+ * @param int $n
+ *
+ * @return int
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function glog(int $n):int {
+
+ if($n < 1){
+ throw new QRCodeException('log('.$n.')');
+ }
+
+ return Polynomial::table[$n][1];
+ }
+
+ /**
+ * @param int $n
+ *
+ * @return int
+ */
+ public function gexp(int $n):int {
+
+ if($n < 0){
+ $n += 255;
+ }
+ elseif($n >= 256){
+ $n -= 255;
+ }
+
+ return Polynomial::table[$n][0];
+ }
+
+}
diff --git a/src/Output/QRImage.php b/src/Output/QRImage.php
index e37c1ca2c..1d9896996 100644
--- a/src/Output/QRImage.php
+++ b/src/Output/QRImage.php
@@ -20,95 +20,77 @@ use chillerlan\QRCode\QRCode;
class QRImage extends QROutputAbstract{
/**
- * @var string
- */
- protected $optionsInterface = QRImageOptions::class;
-
- /**
- * @var array
- */
- protected $types = [
- QRCode::OUTPUT_IMAGE_GIF,
- QRCode::OUTPUT_IMAGE_JPG,
- QRCode::OUTPUT_IMAGE_PNG,
- ];
-
- /**
+ * @todo
* @return string
*/
public function dump():string {
- // clamp input (@todo: determine sane values!)
- $this->options->pixelSize = max(1, min(25, (int)$this->options->pixelSize));
- $this->options->marginSize = max(0, min(25, (int)$this->options->marginSize));
-
- foreach(['fgRed', 'fgGreen', 'fgBlue', 'bgRed', 'bgGreen', 'bgBlue'] as $val){
- $this->options->{$val} = max(0, min(255, (int)$this->options->{$val}));
- }
-
- $length = $this->pixelCount * $this->options->pixelSize + $this->options->marginSize * 2;
+ $length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale;
$image = imagecreatetruecolor($length, $length);
- $foreground = imagecolorallocate($image, $this->options->fgRed, $this->options->fgGreen, $this->options->fgBlue);
- $background = imagecolorallocate($image, $this->options->bgRed, $this->options->bgGreen, $this->options->bgBlue);
+ $background = imagecolorallocate($image, 255, 255, 255);
- if((bool)$this->options->transparent && $this->options->type !== QRCode::OUTPUT_IMAGE_JPG){
+ if((bool)$this->options->imageTransparent && $this->options->outputType !== QRCode::OUTPUT_IMAGE_JPG){
imagecolortransparent($image, $background);
}
imagefilledrectangle($image, 0, 0, $length, $length, $background);
- foreach($this->matrix as $r => $row){
+ foreach($this->matrix->matrix() as $r => $row){
foreach($row as $c => $pixel){
- if($pixel){
- imagefilledrectangle($image,
- $this->options->marginSize + $c * $this->options->pixelSize,
- $this->options->marginSize + $r * $this->options->pixelSize,
- $this->options->marginSize + ($c + 1) * $this->options->pixelSize - 1,
- $this->options->marginSize + ($r + 1) * $this->options->pixelSize - 1,
- $foreground);
- }
+ list($red, $green, $blue) = $this->options->moduleValues[$pixel];
+
+ imagefilledrectangle(
+ $image,
+ $c * $this->options->scale,
+ $r * $this->options->scale,
+ ($c + 1) * $this->options->scale - 1,
+ ($r + 1) * $this->options->scale - 1,
+ imagecolorallocate($image, $red, $green, $blue)
+ );
+
}
}
ob_start();
- switch($this->options->type){
- case QRCode::OUTPUT_IMAGE_JPG:
- // @codeCoverageIgnoreStart
- imagejpeg(
- $image,
- $this->options->cachefile,
- in_array($this->options->jpegQuality, range(0, 100), true)
- ? $this->options->jpegQuality
- : 85
- );
- break;
- // @codeCoverageIgnoreEnd
- case QRCode::OUTPUT_IMAGE_GIF: /** Actually, it's pronounced "DJIFF". *hides* */
- imagegif(
- $image,
- $this->options->cachefile
- );
- break;
- case QRCode::OUTPUT_IMAGE_PNG:
- default:
- imagepng(
- $image,
- $this->options->cachefile,
- in_array($this->options->pngCompression, range(-1, 9), true)
- ? $this->options->pngCompression
- : -1
- );
- }
-
+ $this->{$this->options->outputType ?? 'png'}($image);
$imageData = ob_get_contents();
imagedestroy($image);
+
ob_end_clean();
- if((bool)$this->options->base64){
- $imageData = 'data:image/'.$this->options->type.';base64,'.base64_encode($imageData);
+ if((bool)$this->options->imageBase64){
+ $imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData);
}
return $imageData;
}
+ protected function png(&$image){
+ imagepng(
+ $image,
+ $this->options->cachefile,
+ in_array($this->options->pngCompression, range(-1, 9), true)
+ ? $this->options->pngCompression
+ : -1
+ );
+
+ }
+
+ /**
+ * Jiff - like... JitHub!
+ */
+ protected function gif(&$image){
+ imagegif($image, $this->options->cachefile);
+ }
+
+ protected function jpg(&$image){
+ imagejpeg(
+ $image,
+ $this->options->cachefile,
+ in_array($this->options->jpegQuality, range(0, 100), true)
+ ? $this->options->jpegQuality
+ : 85
+ );
+ }
+
}
diff --git a/src/Output/QRImageOptions.php b/src/Output/QRImageOptions.php
deleted file mode 100644
index 759e2a733..000000000
--- a/src/Output/QRImageOptions.php
+++ /dev/null
@@ -1,101 +0,0 @@
-
- * @copyright 2015 Smiley
- * @license MIT
- */
-
-namespace chillerlan\QRCode\Output;
-
-use chillerlan\QRCode\QRCode;
-
-/**
- * @property string $type
- * @property bool $base64
- * @property int $pixelSize
- * @property int $marginSize
- * @property bool $transparent
- * @property int $fgRed
- * @property int $fgGreen
- * @property int $fgBlue
- * @property int $bgRed
- * @property int $bgGreen
- * @property int $bgBlue
- * @property int $pngCompression
- * @property int $jpegQuality
- */
-class QRImageOptions extends QROutputOptionsAbstract{
-
- /**
- * @var string
- */
- protected $type = QRCode::OUTPUT_IMAGE_PNG;
-
- /**
- * @var bool
- */
- protected $base64 = true;
-
- /**
- * @var int
- */
- protected $pixelSize = 5;
-
- /**
- * @var int
- */
- protected $marginSize = 5;
-
- /**
- * not supported by jpg
- *
- * @var bool
- */
- protected $transparent = true;
-
- /**
- * @var int
- */
- protected $fgRed = 0;
-
- /**
- * @var int
- */
- protected $fgGreen = 0;
-
- /**
- * @var int
- */
- protected $fgBlue = 0;
-
- /**
- * @var int
- */
- protected $bgRed = 255;
-
- /**
- * @var int
- */
- protected $bgGreen = 255;
-
- /**
- * @var int
- */
- protected $bgBlue = 255;
-
- /**
- * @var int
- */
- protected $pngCompression = -1;
-
- /**
- * @var int
- */
- protected $jpegQuality = 85;
-
-}
diff --git a/src/Output/QRMarkup.php b/src/Output/QRMarkup.php
index 116efec92..7f7eb3147 100644
--- a/src/Output/QRMarkup.php
+++ b/src/Output/QRMarkup.php
@@ -19,24 +19,11 @@ use chillerlan\QRCode\QRCode;
*/
class QRMarkup extends QROutputAbstract{
- /**
- * @var string
- */
- protected $optionsInterface = QRMarkupOptions::class;
-
- /**
- * @var array
- */
- protected $types = [
- QRCode::OUTPUT_MARKUP_HTML,
- QRCode::OUTPUT_MARKUP_SVG,
- ];
-
/**
* @return string
*/
public function dump() {
- switch($this->options->type){
+ switch($this->options->outputType){
case QRCode::OUTPUT_MARKUP_SVG : return $this->toSVG();
case QRCode::OUTPUT_MARKUP_HTML:
default:
@@ -50,22 +37,14 @@ class QRMarkup extends QROutputAbstract{
protected function toHTML(){
$html = '';
- foreach($this->matrix as $row){
- // in order to not bloat the output too much, we use the shortest possible valid HTML tags
+ foreach($this->matrix->matrix() as $row){
$html .= '<'.$this->options->htmlRowTag.'>';
- foreach($row as $col){
- $tag = $col
- ? 'b' // dark
- : 'i'; // light
-
- $html .= '<'.$tag.'>'.$tag.'>';
- }
-
- if(!(bool)$this->options->htmlOmitEndTag){
- $html .= ''.$this->options->htmlRowTag.'>';
+ foreach($row as $pixel){
+ $html .= '';
}
+ $html .= (bool)$this->options->htmlOmitEndTag ? ''.$this->options->htmlRowTag.'>' : '';
$html .= $this->options->eol;
}
@@ -84,42 +63,45 @@ class QRMarkup extends QROutputAbstract{
* @return string|bool
*/
protected function toSVG(){
- $length = $this->pixelCount * $this->options->pixelSize + $this->options->marginSize * 2;
- $class = $this->options->cssClass ?: hash('crc32', microtime(true));
+ $length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale;
// svg header
- $svg = '