| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* Class QRDataAbstract |
| 5 |
* |
| 6 |
* @filesource QRDataAbstract.php |
| 7 |
* @created 25.11.2015 |
| 8 |
* @package chillerlan\QRCode\Data |
| 9 |
* @author Smiley <smiley@chillerlan.net> |
| 10 |
* @copyright 2015 Smiley |
| 11 |
* @license MIT |
| 12 |
*/ |
| 13 |
namespace YoastSEO_Vendor\chillerlan\QRCode\Data; |
| 14 |
|
| 15 |
use YoastSEO_Vendor\chillerlan\QRCode\QRCode; |
| 16 |
use YoastSEO_Vendor\chillerlan\QRCode\QRCodeException; |
| 17 |
use YoastSEO_Vendor\chillerlan\QRCode\QROptions; |
| 18 |
use YoastSEO_Vendor\chillerlan\QRCode\Helpers\BitBuffer; |
| 19 |
use YoastSEO_Vendor\chillerlan\QRCode\Helpers\Polynomial; |
| 20 |
use YoastSEO_Vendor\chillerlan\QRCode\Traits\ClassLoader; |
| 21 |
/** |
| 22 |
* Processes the binary data and maps it on a matrix which is then being returned |
| 23 |
*/ |
| 24 |
abstract class QRDataAbstract implements \YoastSEO_Vendor\chillerlan\QRCode\Data\QRDataInterface |
| 25 |
{ |
| 26 |
use ClassLoader; |
| 27 |
/** |
| 28 |
* the string byte count |
| 29 |
* |
| 30 |
* @var int |
| 31 |
*/ |
| 32 |
protected $strlen; |
| 33 |
/** |
| 34 |
* the current data mode: Num, Alphanum, Kanji, Byte |
| 35 |
* |
| 36 |
* @var int |
| 37 |
*/ |
| 38 |
protected $datamode; |
| 39 |
/** |
| 40 |
* mode length bits for the version breakpoints 1-9, 10-26 and 27-40 |
| 41 |
* |
| 42 |
* @var array |
| 43 |
*/ |
| 44 |
protected $lengthBits = [0, 0, 0]; |
| 45 |
/** |
| 46 |
* current QR Code version |
| 47 |
* |
| 48 |
* @var int |
| 49 |
*/ |
| 50 |
protected $version; |
| 51 |
/** |
| 52 |
* the raw data that's being passed to QRMatrix::mapData() |
| 53 |
* |
| 54 |
* @var array |
| 55 |
*/ |
| 56 |
protected $matrixdata; |
| 57 |
/** |
| 58 |
* ECC temp data |
| 59 |
* |
| 60 |
* @var array |
| 61 |
*/ |
| 62 |
protected $ecdata; |
| 63 |
/** |
| 64 |
* ECC temp data |
| 65 |
* |
| 66 |
* @var array |
| 67 |
*/ |
| 68 |
protected $dcdata; |
| 69 |
/** |
| 70 |
* @var \chillerlan\QRCode\QROptions |
| 71 |
*/ |
| 72 |
protected $options; |
| 73 |
/** |
| 74 |
* @var \chillerlan\QRCode\Helpers\BitBuffer |
| 75 |
*/ |
| 76 |
protected $bitBuffer; |
| 77 |
/** |
| 78 |
* QRDataInterface constructor. |
| 79 |
* |
| 80 |
* @param \chillerlan\QRCode\QROptions $options |
| 81 |
* @param string|null $data |
| 82 |
*/ |
| 83 |
public function __construct(\YoastSEO_Vendor\chillerlan\QRCode\QROptions $options, $data = null) |
| 84 |
{ |
| 85 |
$this->options = $options; |
| 86 |
if ($data !== null) { |
| 87 |
$this->setData($data); |
| 88 |
} |
| 89 |
} |
| 90 |
/** |
| 91 |
* Sets the data string (internally called by the constructor) |
| 92 |
* |
| 93 |
* @param string $data |
| 94 |
* |
| 95 |
* @return \chillerlan\QRCode\Data\QRDataInterface |
| 96 |
*/ |
| 97 |
public function setData($data) |
| 98 |
{ |
| 99 |
if ($this->datamode === \YoastSEO_Vendor\chillerlan\QRCode\QRCode::DATA_KANJI) { |
| 100 |
$data = \mb_convert_encoding($data, 'SJIS', \mb_detect_encoding($data)); |
| 101 |
} |
| 102 |
$this->strlen = $this->getLength($data); |
| 103 |
$this->version = $this->options->version === \YoastSEO_Vendor\chillerlan\QRCode\QRCode::VERSION_AUTO ? $this->getMinimumVersion() : $this->options->version; |
| 104 |
$this->matrixdata = $this->writeBitBuffer($data)->maskECC(); |
| 105 |
return $this; |
| 106 |
} |
| 107 |
/** |
| 108 |
* returns a fresh matrix object with the data written for the given $maskPattern |
| 109 |
* |
| 110 |
* @param int $maskPattern |
| 111 |
* @param bool|null $test |
| 112 |
* |
| 113 |
* @return \chillerlan\QRCode\Data\QRMatrix |
| 114 |
*/ |
| 115 |
public function initMatrix($maskPattern, $test = null) |
| 116 |
{ |
| 117 |
/** @var \chillerlan\QRCode\Data\QRMatrix $matrix */ |
| 118 |
$matrix = $this->loadClass(\YoastSEO_Vendor\chillerlan\QRCode\Data\QRMatrix::class, null, $this->version, $this->options->eccLevel); |
| 119 |
return $matrix->setFinderPattern()->setSeparators()->setAlignmentPattern()->setTimingPattern()->setVersionNumber($test)->setFormatInfo($maskPattern, $test)->setDarkModule()->mapData($this->matrixdata, $maskPattern); |
| 120 |
} |
| 121 |
/** |
| 122 |
* returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 |
| 123 |
* |
| 124 |
* @return int |
| 125 |
* @throws \chillerlan\QRCode\Data\QRCodeDataException |
| 126 |
* @codeCoverageIgnore |
| 127 |
*/ |
| 128 |
protected function getLengthBits() |
| 129 |
{ |
| 130 |
foreach ([9, 26, 40] as $key => $breakpoint) { |
| 131 |
if ($this->version <= $breakpoint) { |
| 132 |
return $this->lengthBits[$key]; |
| 133 |
} |
| 134 |
} |
| 135 |
throw new \YoastSEO_Vendor\chillerlan\QRCode\Data\QRCodeDataException('invalid version number: ' . $this->version); |
| 136 |
} |
| 137 |
/** |
| 138 |
* returns the byte count of the $data string |
| 139 |
* |
| 140 |
* @param string $data |
| 141 |
* |
| 142 |
* @return int |
| 143 |
*/ |
| 144 |
protected function getLength($data) |
| 145 |
{ |
| 146 |
return \strlen($data); |
| 147 |
} |
| 148 |
/** |
| 149 |
* returns the minimum version number for the given string |
| 150 |
* |
| 151 |
* @return int |
| 152 |
* @throws \chillerlan\QRCode\Data\QRCodeDataException |
| 153 |
*/ |
| 154 |
protected function getMinimumVersion() |
| 155 |
{ |
| 156 |
// guess the version number within the given range |
| 157 |
foreach (\range(\max(1, $this->options->versionMin), \min($this->options->versionMax, 40)) as $version) { |
| 158 |
$maxlength = self::MAX_LENGTH[$version][\YoastSEO_Vendor\chillerlan\QRCode\QRCode::DATA_MODES[$this->datamode]][\YoastSEO_Vendor\chillerlan\QRCode\QRCode::ECC_MODES[$this->options->eccLevel]]; |
| 159 |
if ($this->strlen <= $maxlength) { |
| 160 |
return $version; |
| 161 |
} |
| 162 |
} |
| 163 |
throw new \YoastSEO_Vendor\chillerlan\QRCode\Data\QRCodeDataException('data exceeds ' . $maxlength . ' characters'); |
| 164 |
} |
| 165 |
/** |
| 166 |
* @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer() |
| 167 |
* |
| 168 |
* @param string $data |
| 169 |
* |
| 170 |
* @return void |
| 171 |
*/ |
| 172 |
protected abstract function write($data); |
| 173 |
/** |
| 174 |
* writes the string data to the BitBuffer |
| 175 |
* |
| 176 |
* @param string $data |
| 177 |
* |
| 178 |
* @return \chillerlan\QRCode\Data\QRDataAbstract |
| 179 |
* @throws \chillerlan\QRCode\QRCodeException |
| 180 |
*/ |
| 181 |
protected function writeBitBuffer($data) |
| 182 |
{ |
| 183 |
$this->bitBuffer = new \YoastSEO_Vendor\chillerlan\QRCode\Helpers\BitBuffer(); |
| 184 |
// @todo: fixme, get real length |
| 185 |
$MAX_BITS = self::MAX_BITS[$this->version][\YoastSEO_Vendor\chillerlan\QRCode\QRCode::ECC_MODES[$this->options->eccLevel]]; |
| 186 |
$this->bitBuffer->clear()->put($this->datamode, 4)->put($this->strlen, $this->getLengthBits()); |
| 187 |
$this->write($data); |
| 188 |
// there was an error writing the BitBuffer data, which is... unlikely. |
| 189 |
if ($this->bitBuffer->length > $MAX_BITS) { |
| 190 |
throw new \YoastSEO_Vendor\chillerlan\QRCode\QRCodeException('code length overflow. (' . $this->bitBuffer->length . ' > ' . $MAX_BITS . 'bit)'); |
| 191 |
// @codeCoverageIgnore |
| 192 |
} |
| 193 |
// end code. |
| 194 |
if ($this->bitBuffer->length + 4 <= $MAX_BITS) { |
| 195 |
$this->bitBuffer->put(0, 4); |
| 196 |
} |
| 197 |
// padding |
| 198 |
while ($this->bitBuffer->length % 8 !== 0) { |
| 199 |
$this->bitBuffer->putBit(\false); |
| 200 |
} |
| 201 |
// padding |
| 202 |
while (\true) { |
| 203 |
if ($this->bitBuffer->length >= $MAX_BITS) { |
| 204 |
break; |
| 205 |
} |
| 206 |
$this->bitBuffer->put(0xec, 8); |
| 207 |
if ($this->bitBuffer->length >= $MAX_BITS) { |
| 208 |
break; |
| 209 |
} |
| 210 |
$this->bitBuffer->put(0x11, 8); |
| 211 |
} |
| 212 |
return $this; |
| 213 |
} |
| 214 |
/** |
| 215 |
* ECC masking |
| 216 |
* |
| 217 |
* @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer() |
| 218 |
* |
| 219 |
* @link http://www.thonky.com/qr-code-tutorial/error-correction-coding |
| 220 |
* |
| 221 |
* @return array |
| 222 |
*/ |
| 223 |
protected function maskECC() |
| 224 |
{ |
| 225 |
list($l1, $l2, $b1, $b2) = self::RSBLOCKS[$this->version][\YoastSEO_Vendor\chillerlan\QRCode\QRCode::ECC_MODES[$this->options->eccLevel]]; |
| 226 |
$rsBlocks = \array_fill(0, $l1, [$b1, $b2]); |
| 227 |
$rsCount = $l1 + $l2; |
| 228 |
$this->ecdata = \array_fill(0, $rsCount, null); |
| 229 |
$this->dcdata = $this->ecdata; |
| 230 |
if ($l2 > 0) { |
| 231 |
$rsBlocks = \array_merge($rsBlocks, \array_fill(0, $l2, [$b1 + 1, $b2 + 1])); |
| 232 |
} |
| 233 |
$totalCodeCount = 0; |
| 234 |
$maxDcCount = 0; |
| 235 |
$maxEcCount = 0; |
| 236 |
$offset = 0; |
| 237 |
foreach ($rsBlocks as $key => $block) { |
| 238 |
list($rsBlockTotal, $dcCount) = $block; |
| 239 |
$ecCount = $rsBlockTotal - $dcCount; |
| 240 |
$maxDcCount = \max($maxDcCount, $dcCount); |
| 241 |
$maxEcCount = \max($maxEcCount, $ecCount); |
| 242 |
$this->dcdata[$key] = \array_fill(0, $dcCount, null); |
| 243 |
foreach ($this->dcdata[$key] as $a => $_z) { |
| 244 |
$this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset]; |
| 245 |
} |
| 246 |
list($num, $add) = $this->poly($key, $ecCount); |
| 247 |
foreach ($this->ecdata[$key] as $c => $_z) { |
| 248 |
$modIndex = $c + $add; |
| 249 |
$this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0; |
| 250 |
} |
| 251 |
$offset += $dcCount; |
| 252 |
$totalCodeCount += $rsBlockTotal; |
| 253 |
} |
| 254 |
$data = \array_fill(0, $totalCodeCount, null); |
| 255 |
$index = 0; |
| 256 |
$mask = function ($arr, $count) use(&$data, &$index, $rsCount) { |
| 257 |
for ($x = 0; $x < $count; $x++) { |
| 258 |
for ($y = 0; $y < $rsCount; $y++) { |
| 259 |
if ($x < \count($arr[$y])) { |
| 260 |
$data[$index] = $arr[$y][$x]; |
| 261 |
$index++; |
| 262 |
} |
| 263 |
} |
| 264 |
} |
| 265 |
}; |
| 266 |
$mask($this->dcdata, $maxDcCount); |
| 267 |
$mask($this->ecdata, $maxEcCount); |
| 268 |
return $data; |
| 269 |
} |
| 270 |
/** |
| 271 |
* @param int $key |
| 272 |
* @param int $count |
| 273 |
* |
| 274 |
* @return int[] |
| 275 |
*/ |
| 276 |
protected function poly($key, $count) |
| 277 |
{ |
| 278 |
$rsPoly = new \YoastSEO_Vendor\chillerlan\QRCode\Helpers\Polynomial(); |
| 279 |
$modPoly = new \YoastSEO_Vendor\chillerlan\QRCode\Helpers\Polynomial(); |
| 280 |
for ($i = 0; $i < $count; $i++) { |
| 281 |
$modPoly->setNum([1, $modPoly->gexp($i)]); |
| 282 |
$rsPoly->multiply($modPoly->getNum()); |
| 283 |
} |
| 284 |
$rsPolyCount = \count($rsPoly->getNum()); |
| 285 |
$modPoly->setNum($this->dcdata[$key], $rsPolyCount - 1)->mod($rsPoly->getNum()); |
| 286 |
$this->ecdata[$key] = \array_fill(0, $rsPolyCount - 1, null); |
| 287 |
$num = $modPoly->getNum(); |
| 288 |
return [$num, \count($num) - \count($this->ecdata[$key])]; |
| 289 |
} |
| 290 |
} |
| 291 |
|