作者 郭文星

13

正在显示 32 个修改的文件 包含 4882 行增加0 行删除

要显示太多修改。

为保证性能只显示 32 of 32+ 个文件。

  1 +{"files":[],"license":"regular","licenseto":"15629","licensekey":"HiXjw0UmOMWv1CJ4 p+tHvep\/xS0jC5mBuNA3jQ==","domains":["netcar.com"],"licensecodes":[],"validations":["9c3b042fdd8c2e4e21e4da30dcc79ba1"]}
  1 +<?php
  2 +
  3 +namespace addons\qrcode;
  4 +
  5 +use think\Addons;
  6 +use think\Loader;
  7 +
  8 +/**
  9 + * 二维码生成
  10 + */
  11 +class Qrcode extends Addons
  12 +{
  13 +
  14 + /**
  15 + * 插件安装方法
  16 + * @return bool
  17 + */
  18 + public function install()
  19 + {
  20 + return true;
  21 + }
  22 +
  23 + /**
  24 + * 插件卸载方法
  25 + * @return bool
  26 + */
  27 + public function uninstall()
  28 + {
  29 + return true;
  30 + }
  31 +
  32 + /**
  33 + * 添加命名空间
  34 + */
  35 + public function appInit()
  36 + {
  37 + if (!class_exists('\BaconQrCode\Writer')) {
  38 + Loader::addNamespace('BaconQrCode', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'BaconQrCode' . DS);
  39 + }
  40 + if (!class_exists('\Endroid\QrCode\QrCode')) {
  41 + Loader::addNamespace('Endroid', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'Endroid' . DS);
  42 + }
  43 + if (!class_exists('\MyCLabs\Enum\Enum')) {
  44 + Loader::addNamespace('MyCLabs', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'MyCLabs' . DS);
  45 + }
  46 + if (!class_exists('\DASPRiD\Enum\EnumMap')) {
  47 + Loader::addNamespace('DASPRiD', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'DASPRiD' . DS);
  48 + }
  49 + }
  50 +
  51 +}
  1 +<?php
  2 +
  3 +return [
  4 + [
  5 + 'name' => 'text',
  6 + 'title' => '默认文本',
  7 + 'type' => 'string',
  8 + 'content' => [],
  9 + 'value' => 'Hello world!',
  10 + 'rule' => 'required',
  11 + 'msg' => '',
  12 + 'tip' => '',
  13 + 'ok' => '',
  14 + 'extend' => '',
  15 + ],
  16 + [
  17 + 'name' => 'size',
  18 + 'title' => '默认宽高',
  19 + 'type' => 'number',
  20 + 'content' => [],
  21 + 'value' => '300',
  22 + 'rule' => 'required',
  23 + 'msg' => '',
  24 + 'tip' => '',
  25 + 'ok' => '',
  26 + 'extend' => '',
  27 + ],
  28 + [
  29 + 'name' => 'padding',
  30 + 'title' => '默认边距',
  31 + 'type' => 'number',
  32 + 'content' => [],
  33 + 'value' => '15',
  34 + 'rule' => 'required',
  35 + 'msg' => '',
  36 + 'tip' => '',
  37 + 'ok' => '',
  38 + 'extend' => '',
  39 + ],
  40 + [
  41 + 'name' => 'format',
  42 + 'title' => '默认格式',
  43 + 'type' => 'radio',
  44 + 'content' => [
  45 + 'png' => 'PNG',
  46 + 'svg' => 'SVG(不支持标签)',
  47 + ],
  48 + 'value' => 'png',
  49 + 'rule' => 'required',
  50 + 'msg' => '',
  51 + 'tip' => '',
  52 + 'ok' => '',
  53 + 'extend' => '',
  54 + ],
  55 + [
  56 + 'name' => 'errorlevel',
  57 + 'title' => '容错级别',
  58 + 'type' => 'radio',
  59 + 'content' => [
  60 + 'low' => '低',
  61 + 'medium' => '中',
  62 + 'quartile' => '高',
  63 + 'high' => '超高',
  64 + ],
  65 + 'value' => 'medium',
  66 + 'rule' => 'required',
  67 + 'msg' => '',
  68 + 'tip' => '',
  69 + 'ok' => '',
  70 + 'extend' => '',
  71 + ],
  72 + [
  73 + 'name' => 'foreground',
  74 + 'title' => '前景色',
  75 + 'type' => 'string',
  76 + 'content' => [],
  77 + 'value' => '#000000',
  78 + 'rule' => 'required',
  79 + 'msg' => '',
  80 + 'tip' => '',
  81 + 'ok' => '',
  82 + 'extend' => '',
  83 + ],
  84 + [
  85 + 'name' => 'background',
  86 + 'title' => '背景色',
  87 + 'type' => 'string',
  88 + 'content' => [],
  89 + 'value' => '#ffffff',
  90 + 'rule' => 'required',
  91 + 'msg' => '',
  92 + 'tip' => '',
  93 + 'ok' => '',
  94 + 'extend' => '',
  95 + ],
  96 + [
  97 + 'name' => 'label',
  98 + 'title' => '默认标签',
  99 + 'type' => 'string',
  100 + 'content' => [],
  101 + 'value' => '',
  102 + 'rule' => '',
  103 + 'msg' => '',
  104 + 'tip' => '',
  105 + 'ok' => '',
  106 + 'extend' => '',
  107 + ],
  108 + [
  109 + 'name' => 'labelfontsize',
  110 + 'title' => '标签字体大小',
  111 + 'type' => 'number',
  112 + 'content' => [],
  113 + 'value' => '14',
  114 + 'rule' => '',
  115 + 'msg' => '',
  116 + 'tip' => '',
  117 + 'ok' => '',
  118 + 'extend' => '',
  119 + ],
  120 + [
  121 + 'name' => 'labelfontpath',
  122 + 'title' => '标签字体',
  123 + 'type' => 'file',
  124 + 'content' => [],
  125 + 'value' => '/assets/fonts/SourceHanSansK-Regular.ttf',
  126 + 'rule' => 'required',
  127 + 'msg' => '',
  128 + 'tip' => '',
  129 + 'ok' => '',
  130 + 'extend' => '',
  131 + ],
  132 + [
  133 + 'name' => 'labelalignment',
  134 + 'title' => '标签对齐方式',
  135 + 'type' => 'radio',
  136 + 'content' => [
  137 + 'left' => '左',
  138 + 'center' => '居中',
  139 + 'right' => '右',
  140 + ],
  141 + 'value' => 'center',
  142 + 'rule' => '',
  143 + 'msg' => '',
  144 + 'tip' => '',
  145 + 'ok' => '',
  146 + 'extend' => '',
  147 + ],
  148 + [
  149 + 'name' => 'logo',
  150 + 'title' => '默认显示Logo',
  151 + 'type' => 'radio',
  152 + 'content' => [
  153 + '否',
  154 + '是',
  155 + ],
  156 + 'value' => '0',
  157 + 'rule' => 'required',
  158 + 'msg' => '',
  159 + 'tip' => '',
  160 + 'ok' => '',
  161 + 'extend' => '',
  162 + ],
  163 + [
  164 + 'name' => 'logopath',
  165 + 'title' => 'Logo图片',
  166 + 'type' => 'image',
  167 + 'content' => [],
  168 + 'value' => '/assets/img/qrcode.png',
  169 + 'rule' => 'required',
  170 + 'msg' => '',
  171 + 'tip' => '',
  172 + 'ok' => '',
  173 + 'extend' => '',
  174 + ],
  175 + [
  176 + 'name' => 'logosize',
  177 + 'title' => 'Logo大小',
  178 + 'type' => 'number',
  179 + 'content' => [],
  180 + 'value' => '50',
  181 + 'rule' => 'required',
  182 + 'msg' => '',
  183 + 'tip' => '',
  184 + 'ok' => '',
  185 + 'extend' => '',
  186 + ],
  187 + [
  188 + 'name' => 'writefile',
  189 + 'title' => '写入文件',
  190 + 'type' => 'radio',
  191 + 'content' => [
  192 + '否',
  193 + '是',
  194 + ],
  195 + 'value' => '0',
  196 + 'rule' => 'required',
  197 + 'msg' => '',
  198 + 'tip' => '',
  199 + 'ok' => '',
  200 + 'extend' => '',
  201 + ],
  202 + [
  203 + 'name' => 'limitreferer',
  204 + 'title' => '防盗链配置',
  205 + 'type' => 'radio',
  206 + 'content' => [
  207 + '否',
  208 + '是',
  209 + ],
  210 + 'value' => '0',
  211 + 'rule' => 'required',
  212 + 'msg' => '',
  213 + 'tip' => '',
  214 + 'ok' => '',
  215 + 'extend' => '',
  216 + ],
  217 + [
  218 + 'name' => 'allowemptyreferer',
  219 + 'title' => '允许空referer',
  220 + 'visible' => 'limitreferer=1',
  221 + 'type' => 'radio',
  222 + 'content' => [
  223 + '否',
  224 + '是',
  225 + ],
  226 + 'value' => '0',
  227 + 'rule' => 'required',
  228 + 'msg' => '',
  229 + 'tip' => '',
  230 + 'ok' => '',
  231 + 'extend' => '',
  232 + ],
  233 + [
  234 + 'name' => 'allowrefererlist',
  235 + 'title' => '允许的域名列表',
  236 + 'visible' => 'limitreferer=1',
  237 + 'type' => 'text',
  238 + 'content' => [
  239 + ],
  240 + 'value' => '',
  241 + 'rule' => '',
  242 + 'msg' => '',
  243 + 'tip' => '一行一个域名,支持泛域名,*表示所有域名',
  244 + 'ok' => '',
  245 + 'extend' => '',
  246 + ],
  247 + [
  248 + 'name' => 'rewrite',
  249 + 'title' => '伪静态',
  250 + 'type' => 'array',
  251 + 'content' => [],
  252 + 'value' => [
  253 + 'index/index' => '/qrcode$',
  254 + 'index/build' => '/qrcode/build$',
  255 + ],
  256 + 'rule' => 'required',
  257 + 'msg' => '',
  258 + 'tip' => '',
  259 + 'ok' => '',
  260 + 'extend' => '',
  261 + ],
  262 +];
  1 +<?php
  2 +
  3 +namespace addons\qrcode\controller;
  4 +
  5 +use think\addons\Controller;
  6 +use think\exception\HttpResponseException;
  7 +use think\Response;
  8 +
  9 +/**
  10 + * 二维码生成
  11 + *
  12 + */
  13 +class Index extends Controller
  14 +{
  15 + public function index()
  16 + {
  17 + return $this->view->fetch();
  18 + }
  19 +
  20 + // 生成二维码
  21 + public function build()
  22 + {
  23 + $config = get_addon_config('qrcode');
  24 + if (isset($config['limitreferer']) && $config['limitreferer']) {
  25 + $referer = $this->request->server('HTTP_REFERER', '');
  26 + $refererInfo = parse_url($referer);
  27 + $refererHost = $referer && $refererInfo ? $refererInfo['host'] : '';
  28 + $refererHostArr = explode('.', $refererHost);
  29 + $wildcardDomain = '';
  30 + if (count($refererHostArr) > 2) {
  31 + $refererHostArr[0] = '*';
  32 + $wildcardDomain = implode('.', $refererHostArr);
  33 + }
  34 + $allowRefererList = $config['allowrefererlist'] ?? '';
  35 + $domainArr = explode("\n", str_replace("\r", "", $allowRefererList));
  36 + $domainArr = array_filter(array_unique($domainArr));
  37 + $domainArr[] = request()->host(true);
  38 +
  39 + $inAllowList = false;
  40 +
  41 + if (in_array('*', $domainArr) || ($refererHost && in_array($refererHost, $domainArr)) || ($wildcardDomain && in_array($wildcardDomain, $domainArr))) {
  42 + $inAllowList = true;
  43 + }
  44 +
  45 + if (!$inAllowList && (!$referer && $config['allowemptyreferer'])) {
  46 + $inAllowList = true;
  47 + }
  48 +
  49 + if (!$inAllowList) {
  50 + $response = Response::create('暂无权限', 'html', 403);
  51 + throw new HttpResponseException($response);
  52 + }
  53 + }
  54 + $params = $this->request->get();
  55 + $params = array_intersect_key($params, array_flip(['text', 'size', 'padding', 'errorlevel', 'foreground', 'background', 'logo', 'logosize', 'logopath', 'label', 'labelfontsize', 'labelalignment']));
  56 +
  57 + $params['text'] = $this->request->get('text', $config['text'], 'trim');
  58 + $params['label'] = $this->request->get('label', $config['label'], 'trim');
  59 +
  60 + $qrCode = \addons\qrcode\library\Service::qrcode($params);
  61 +
  62 + $mimetype = $config['format'] == 'png' ? 'image/png' : 'image/svg+xml';
  63 +
  64 + $response = Response::create()->header("Content-Type", $mimetype);
  65 +
  66 + // 直接显示二维码
  67 + header('Content-Type: ' . $qrCode->getContentType());
  68 + $response->content($qrCode->writeString());
  69 +
  70 + // 写入到文件
  71 + if ($config['writefile']) {
  72 + $qrcodePath = ROOT_PATH . 'public/uploads/qrcode/';
  73 + if (!is_dir($qrcodePath)) {
  74 + @mkdir($qrcodePath);
  75 + }
  76 + if (is_really_writable($qrcodePath)) {
  77 + $filePath = $qrcodePath . md5(implode('', $params)) . '.' . $config['format'];
  78 + $qrCode->writeFile($filePath);
  79 + }
  80 + }
  81 +
  82 + return $response;
  83 + }
  84 +}
  1 +name = qrcode
  2 +title = 二维码生成
  3 +intro = 生成二维码插件
  4 +author = FastAdmin
  5 +website = https://www.fastadmin.net
  6 +version = 1.0.7
  7 +state = 1
  8 +url = /addons/qrcode
  9 +license = regular
  10 +licenseto = 15629
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use BaconQrCode\Exception\InvalidArgumentException;
  7 +use SplFixedArray;
  8 +
  9 +/**
  10 + * A simple, fast array of bits.
  11 + */
  12 +final class BitArray
  13 +{
  14 + /**
  15 + * Bits represented as an array of integers.
  16 + *
  17 + * @var SplFixedArray<int>
  18 + */
  19 + private $bits;
  20 +
  21 + /**
  22 + * Size of the bit array in bits.
  23 + *
  24 + * @var int
  25 + */
  26 + private $size;
  27 +
  28 + /**
  29 + * Creates a new bit array with a given size.
  30 + */
  31 + public function __construct(int $size = 0)
  32 + {
  33 + $this->size = $size;
  34 + $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
  35 + }
  36 +
  37 + /**
  38 + * Gets the size in bits.
  39 + */
  40 + public function getSize() : int
  41 + {
  42 + return $this->size;
  43 + }
  44 +
  45 + /**
  46 + * Gets the size in bytes.
  47 + */
  48 + public function getSizeInBytes() : int
  49 + {
  50 + return ($this->size + 7) >> 3;
  51 + }
  52 +
  53 + /**
  54 + * Ensures that the array has a minimum capacity.
  55 + */
  56 + public function ensureCapacity(int $size) : void
  57 + {
  58 + if ($size > count($this->bits) << 5) {
  59 + $this->bits->setSize(($size + 31) >> 5);
  60 + }
  61 + }
  62 +
  63 + /**
  64 + * Gets a specific bit.
  65 + */
  66 + public function get(int $i) : bool
  67 + {
  68 + return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f)));
  69 + }
  70 +
  71 + /**
  72 + * Sets a specific bit.
  73 + */
  74 + public function set(int $i) : void
  75 + {
  76 + $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
  77 + }
  78 +
  79 + /**
  80 + * Flips a specific bit.
  81 + */
  82 + public function flip(int $i) : void
  83 + {
  84 + $this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
  85 + }
  86 +
  87 + /**
  88 + * Gets the next set bit position from a given position.
  89 + */
  90 + public function getNextSet(int $from) : int
  91 + {
  92 + if ($from >= $this->size) {
  93 + return $this->size;
  94 + }
  95 +
  96 + $bitsOffset = $from >> 5;
  97 + $currentBits = $this->bits[$bitsOffset];
  98 + $bitsLength = count($this->bits);
  99 + $currentBits &= ~((1 << ($from & 0x1f)) - 1);
  100 +
  101 + while (0 === $currentBits) {
  102 + if (++$bitsOffset === $bitsLength) {
  103 + return $this->size;
  104 + }
  105 +
  106 + $currentBits = $this->bits[$bitsOffset];
  107 + }
  108 +
  109 + $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
  110 + return $result > $this->size ? $this->size : $result;
  111 + }
  112 +
  113 + /**
  114 + * Gets the next unset bit position from a given position.
  115 + */
  116 + public function getNextUnset(int $from) : int
  117 + {
  118 + if ($from >= $this->size) {
  119 + return $this->size;
  120 + }
  121 +
  122 + $bitsOffset = $from >> 5;
  123 + $currentBits = ~$this->bits[$bitsOffset];
  124 + $bitsLength = count($this->bits);
  125 + $currentBits &= ~((1 << ($from & 0x1f)) - 1);
  126 +
  127 + while (0 === $currentBits) {
  128 + if (++$bitsOffset === $bitsLength) {
  129 + return $this->size;
  130 + }
  131 +
  132 + $currentBits = ~$this->bits[$bitsOffset];
  133 + }
  134 +
  135 + $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
  136 + return $result > $this->size ? $this->size : $result;
  137 + }
  138 +
  139 + /**
  140 + * Sets a bulk of bits.
  141 + */
  142 + public function setBulk(int $i, int $newBits) : void
  143 + {
  144 + $this->bits[$i >> 5] = $newBits;
  145 + }
  146 +
  147 + /**
  148 + * Sets a range of bits.
  149 + *
  150 + * @throws InvalidArgumentException if end is smaller than start
  151 + */
  152 + public function setRange(int $start, int $end) : void
  153 + {
  154 + if ($end < $start) {
  155 + throw new InvalidArgumentException('End must be greater or equal to start');
  156 + }
  157 +
  158 + if ($end === $start) {
  159 + return;
  160 + }
  161 +
  162 + --$end;
  163 +
  164 + $firstInt = $start >> 5;
  165 + $lastInt = $end >> 5;
  166 +
  167 + for ($i = $firstInt; $i <= $lastInt; ++$i) {
  168 + $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
  169 + $lastBit = $i < $lastInt ? 31 : $end & 0x1f;
  170 +
  171 + if (0 === $firstBit && 31 === $lastBit) {
  172 + $mask = 0x7fffffff;
  173 + } else {
  174 + $mask = 0;
  175 +
  176 + for ($j = $firstBit; $j < $lastBit; ++$j) {
  177 + $mask |= 1 << $j;
  178 + }
  179 + }
  180 +
  181 + $this->bits[$i] = $this->bits[$i] | $mask;
  182 + }
  183 + }
  184 +
  185 + /**
  186 + * Clears the bit array, unsetting every bit.
  187 + */
  188 + public function clear() : void
  189 + {
  190 + $bitsLength = count($this->bits);
  191 +
  192 + for ($i = 0; $i < $bitsLength; ++$i) {
  193 + $this->bits[$i] = 0;
  194 + }
  195 + }
  196 +
  197 + /**
  198 + * Checks if a range of bits is set or not set.
  199 +
  200 + * @throws InvalidArgumentException if end is smaller than start
  201 + */
  202 + public function isRange(int $start, int $end, bool $value) : bool
  203 + {
  204 + if ($end < $start) {
  205 + throw new InvalidArgumentException('End must be greater or equal to start');
  206 + }
  207 +
  208 + if ($end === $start) {
  209 + return true;
  210 + }
  211 +
  212 + --$end;
  213 +
  214 + $firstInt = $start >> 5;
  215 + $lastInt = $end >> 5;
  216 +
  217 + for ($i = $firstInt; $i <= $lastInt; ++$i) {
  218 + $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
  219 + $lastBit = $i < $lastInt ? 31 : $end & 0x1f;
  220 +
  221 + if (0 === $firstBit && 31 === $lastBit) {
  222 + $mask = 0x7fffffff;
  223 + } else {
  224 + $mask = 0;
  225 +
  226 + for ($j = $firstBit; $j <= $lastBit; ++$j) {
  227 + $mask |= 1 << $j;
  228 + }
  229 + }
  230 +
  231 + if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
  232 + return false;
  233 + }
  234 + }
  235 +
  236 + return true;
  237 + }
  238 +
  239 + /**
  240 + * Appends a bit to the array.
  241 + */
  242 + public function appendBit(bool $bit) : void
  243 + {
  244 + $this->ensureCapacity($this->size + 1);
  245 +
  246 + if ($bit) {
  247 + $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
  248 + }
  249 +
  250 + ++$this->size;
  251 + }
  252 +
  253 + /**
  254 + * Appends a number of bits (up to 32) to the array.
  255 +
  256 + * @throws InvalidArgumentException if num bits is not between 0 and 32
  257 + */
  258 + public function appendBits(int $value, int $numBits) : void
  259 + {
  260 + if ($numBits < 0 || $numBits > 32) {
  261 + throw new InvalidArgumentException('Num bits must be between 0 and 32');
  262 + }
  263 +
  264 + $this->ensureCapacity($this->size + $numBits);
  265 +
  266 + for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
  267 + $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
  268 + }
  269 + }
  270 +
  271 + /**
  272 + * Appends another bit array to this array.
  273 + */
  274 + public function appendBitArray(self $other) : void
  275 + {
  276 + $otherSize = $other->getSize();
  277 + $this->ensureCapacity($this->size + $other->getSize());
  278 +
  279 + for ($i = 0; $i < $otherSize; ++$i) {
  280 + $this->appendBit($other->get($i));
  281 + }
  282 + }
  283 +
  284 + /**
  285 + * Makes an exclusive-or comparision on the current bit array.
  286 + *
  287 + * @throws InvalidArgumentException if sizes don't match
  288 + */
  289 + public function xorBits(self $other) : void
  290 + {
  291 + $bitsLength = count($this->bits);
  292 + $otherBits = $other->getBitArray();
  293 +
  294 + if ($bitsLength !== count($otherBits)) {
  295 + throw new InvalidArgumentException('Sizes don\'t match');
  296 + }
  297 +
  298 + for ($i = 0; $i < $bitsLength; ++$i) {
  299 + $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
  300 + }
  301 + }
  302 +
  303 + /**
  304 + * Converts the bit array to a byte array.
  305 + *
  306 + * @return SplFixedArray<int>
  307 + */
  308 + public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray
  309 + {
  310 + $bytes = new SplFixedArray($numBytes);
  311 +
  312 + for ($i = 0; $i < $numBytes; ++$i) {
  313 + $byte = 0;
  314 +
  315 + for ($j = 0; $j < 8; ++$j) {
  316 + if ($this->get($bitOffset)) {
  317 + $byte |= 1 << (7 - $j);
  318 + }
  319 +
  320 + ++$bitOffset;
  321 + }
  322 +
  323 + $bytes[$i] = $byte;
  324 + }
  325 +
  326 + return $bytes;
  327 + }
  328 +
  329 + /**
  330 + * Gets the internal bit array.
  331 + *
  332 + * @return SplFixedArray<int>
  333 + */
  334 + public function getBitArray() : SplFixedArray
  335 + {
  336 + return $this->bits;
  337 + }
  338 +
  339 + /**
  340 + * Reverses the array.
  341 + */
  342 + public function reverse() : void
  343 + {
  344 + $newBits = new SplFixedArray(count($this->bits));
  345 +
  346 + for ($i = 0; $i < $this->size; ++$i) {
  347 + if ($this->get($this->size - $i - 1)) {
  348 + $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
  349 + }
  350 + }
  351 +
  352 + $this->bits = $newBits;
  353 + }
  354 +
  355 + /**
  356 + * Returns a string representation of the bit array.
  357 + */
  358 + public function __toString() : string
  359 + {
  360 + $result = '';
  361 +
  362 + for ($i = 0; $i < $this->size; ++$i) {
  363 + if (0 === ($i & 0x07)) {
  364 + $result .= ' ';
  365 + }
  366 +
  367 + $result .= $this->get($i) ? 'X' : '.';
  368 + }
  369 +
  370 + return $result;
  371 + }
  372 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use BaconQrCode\Exception\InvalidArgumentException;
  7 +use SplFixedArray;
  8 +
  9 +/**
  10 + * Bit matrix.
  11 + *
  12 + * Represents a 2D matrix of bits. In function arguments below, and throughout
  13 + * the common module, x is the column position, and y is the row position. The
  14 + * ordering is always x, y. The origin is at the top-left.
  15 + */
  16 +class BitMatrix
  17 +{
  18 + /**
  19 + * Width of the bit matrix.
  20 + *
  21 + * @var int
  22 + */
  23 + private $width;
  24 +
  25 + /**
  26 + * Height of the bit matrix.
  27 + *
  28 + * @var int
  29 + */
  30 + private $height;
  31 +
  32 + /**
  33 + * Size in bits of each individual row.
  34 + *
  35 + * @var int
  36 + */
  37 + private $rowSize;
  38 +
  39 + /**
  40 + * Bits representation.
  41 + *
  42 + * @var SplFixedArray<int>
  43 + */
  44 + private $bits;
  45 +
  46 + /**
  47 + * @throws InvalidArgumentException if a dimension is smaller than zero
  48 + */
  49 + public function __construct(int $width, int $height = null)
  50 + {
  51 + if (null === $height) {
  52 + $height = $width;
  53 + }
  54 +
  55 + if ($width < 1 || $height < 1) {
  56 + throw new InvalidArgumentException('Both dimensions must be greater than zero');
  57 + }
  58 +
  59 + $this->width = $width;
  60 + $this->height = $height;
  61 + $this->rowSize = ($width + 31) >> 5;
  62 + $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0));
  63 + }
  64 +
  65 + /**
  66 + * Gets the requested bit, where true means black.
  67 + */
  68 + public function get(int $x, int $y) : bool
  69 + {
  70 + $offset = $y * $this->rowSize + ($x >> 5);
  71 + return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1);
  72 + }
  73 +
  74 + /**
  75 + * Sets the given bit to true.
  76 + */
  77 + public function set(int $x, int $y) : void
  78 + {
  79 + $offset = $y * $this->rowSize + ($x >> 5);
  80 + $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
  81 + }
  82 +
  83 + /**
  84 + * Flips the given bit.
  85 + */
  86 + public function flip(int $x, int $y) : void
  87 + {
  88 + $offset = $y * $this->rowSize + ($x >> 5);
  89 + $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
  90 + }
  91 +
  92 + /**
  93 + * Clears all bits (set to false).
  94 + */
  95 + public function clear() : void
  96 + {
  97 + $max = count($this->bits);
  98 +
  99 + for ($i = 0; $i < $max; ++$i) {
  100 + $this->bits[$i] = 0;
  101 + }
  102 + }
  103 +
  104 + /**
  105 + * Sets a square region of the bit matrix to true.
  106 + *
  107 + * @throws InvalidArgumentException if left or top are negative
  108 + * @throws InvalidArgumentException if width or height are smaller than 1
  109 + * @throws InvalidArgumentException if region does not fit into the matix
  110 + */
  111 + public function setRegion(int $left, int $top, int $width, int $height) : void
  112 + {
  113 + if ($top < 0 || $left < 0) {
  114 + throw new InvalidArgumentException('Left and top must be non-negative');
  115 + }
  116 +
  117 + if ($height < 1 || $width < 1) {
  118 + throw new InvalidArgumentException('Width and height must be at least 1');
  119 + }
  120 +
  121 + $right = $left + $width;
  122 + $bottom = $top + $height;
  123 +
  124 + if ($bottom > $this->height || $right > $this->width) {
  125 + throw new InvalidArgumentException('The region must fit inside the matrix');
  126 + }
  127 +
  128 + for ($y = $top; $y < $bottom; ++$y) {
  129 + $offset = $y * $this->rowSize;
  130 +
  131 + for ($x = $left; $x < $right; ++$x) {
  132 + $index = $offset + ($x >> 5);
  133 + $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
  134 + }
  135 + }
  136 + }
  137 +
  138 + /**
  139 + * A fast method to retrieve one row of data from the matrix as a BitArray.
  140 + */
  141 + public function getRow(int $y, BitArray $row = null) : BitArray
  142 + {
  143 + if (null === $row || $row->getSize() < $this->width) {
  144 + $row = new BitArray($this->width);
  145 + }
  146 +
  147 + $offset = $y * $this->rowSize;
  148 +
  149 + for ($x = 0; $x < $this->rowSize; ++$x) {
  150 + $row->setBulk($x << 5, $this->bits[$offset + $x]);
  151 + }
  152 +
  153 + return $row;
  154 + }
  155 +
  156 + /**
  157 + * Sets a row of data from a BitArray.
  158 + */
  159 + public function setRow(int $y, BitArray $row) : void
  160 + {
  161 + $bits = $row->getBitArray();
  162 +
  163 + for ($i = 0; $i < $this->rowSize; ++$i) {
  164 + $this->bits[$y * $this->rowSize + $i] = $bits[$i];
  165 + }
  166 + }
  167 +
  168 + /**
  169 + * This is useful in detecting the enclosing rectangle of a 'pure' barcode.
  170 + *
  171 + * @return int[]|null
  172 + */
  173 + public function getEnclosingRectangle() : ?array
  174 + {
  175 + $left = $this->width;
  176 + $top = $this->height;
  177 + $right = -1;
  178 + $bottom = -1;
  179 +
  180 + for ($y = 0; $y < $this->height; ++$y) {
  181 + for ($x32 = 0; $x32 < $this->rowSize; ++$x32) {
  182 + $bits = $this->bits[$y * $this->rowSize + $x32];
  183 +
  184 + if (0 !== $bits) {
  185 + if ($y < $top) {
  186 + $top = $y;
  187 + }
  188 +
  189 + if ($y > $bottom) {
  190 + $bottom = $y;
  191 + }
  192 +
  193 + if ($x32 * 32 < $left) {
  194 + $bit = 0;
  195 +
  196 + while (($bits << (31 - $bit)) === 0) {
  197 + $bit++;
  198 + }
  199 +
  200 + if (($x32 * 32 + $bit) < $left) {
  201 + $left = $x32 * 32 + $bit;
  202 + }
  203 + }
  204 + }
  205 +
  206 + if ($x32 * 32 + 31 > $right) {
  207 + $bit = 31;
  208 +
  209 + while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
  210 + --$bit;
  211 + }
  212 +
  213 + if (($x32 * 32 + $bit) > $right) {
  214 + $right = $x32 * 32 + $bit;
  215 + }
  216 + }
  217 + }
  218 + }
  219 +
  220 + $width = $right - $left;
  221 + $height = $bottom - $top;
  222 +
  223 + if ($width < 0 || $height < 0) {
  224 + return null;
  225 + }
  226 +
  227 + return [$left, $top, $width, $height];
  228 + }
  229 +
  230 + /**
  231 + * Gets the most top left set bit.
  232 + *
  233 + * This is useful in detecting a corner of a 'pure' barcode.
  234 + *
  235 + * @return int[]|null
  236 + */
  237 + public function getTopLeftOnBit() : ?array
  238 + {
  239 + $bitsOffset = 0;
  240 +
  241 + while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) {
  242 + ++$bitsOffset;
  243 + }
  244 +
  245 + if (count($this->bits) === $bitsOffset) {
  246 + return null;
  247 + }
  248 +
  249 + $x = intdiv($bitsOffset, $this->rowSize);
  250 + $y = ($bitsOffset % $this->rowSize) << 5;
  251 +
  252 + $bits = $this->bits[$bitsOffset];
  253 + $bit = 0;
  254 +
  255 + while (0 === ($bits << (31 - $bit))) {
  256 + ++$bit;
  257 + }
  258 +
  259 + $x += $bit;
  260 +
  261 + return [$x, $y];
  262 + }
  263 +
  264 + /**
  265 + * Gets the most bottom right set bit.
  266 + *
  267 + * This is useful in detecting a corner of a 'pure' barcode.
  268 + *
  269 + * @return int[]|null
  270 + */
  271 + public function getBottomRightOnBit() : ?array
  272 + {
  273 + $bitsOffset = count($this->bits) - 1;
  274 +
  275 + while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) {
  276 + --$bitsOffset;
  277 + }
  278 +
  279 + if ($bitsOffset < 0) {
  280 + return null;
  281 + }
  282 +
  283 + $x = intdiv($bitsOffset, $this->rowSize);
  284 + $y = ($bitsOffset % $this->rowSize) << 5;
  285 +
  286 + $bits = $this->bits[$bitsOffset];
  287 + $bit = 0;
  288 +
  289 + while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
  290 + --$bit;
  291 + }
  292 +
  293 + $x += $bit;
  294 +
  295 + return [$x, $y];
  296 + }
  297 +
  298 + /**
  299 + * Gets the width of the matrix,
  300 + */
  301 + public function getWidth() : int
  302 + {
  303 + return $this->width;
  304 + }
  305 +
  306 + /**
  307 + * Gets the height of the matrix.
  308 + */
  309 + public function getHeight() : int
  310 + {
  311 + return $this->height;
  312 + }
  313 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +/**
  7 + * General bit utilities.
  8 + *
  9 + * All utility methods are based on 32-bit integers and also work on 64-bit
  10 + * systems.
  11 + */
  12 +final class BitUtils
  13 +{
  14 + private function __construct()
  15 + {
  16 + }
  17 +
  18 + /**
  19 + * Performs an unsigned right shift.
  20 + *
  21 + * This is the same as the unsigned right shift operator ">>>" in other
  22 + * languages.
  23 + */
  24 + public static function unsignedRightShift(int $a, int $b) : int
  25 + {
  26 + return (
  27 + $a >= 0
  28 + ? $a >> $b
  29 + : (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
  30 + );
  31 + }
  32 +
  33 + /**
  34 + * Gets the number of trailing zeros.
  35 + */
  36 + public static function numberOfTrailingZeros(int $i) : int
  37 + {
  38 + $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
  39 + return $lastPos === false ? 32 : 31 - $lastPos;
  40 + }
  41 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use BaconQrCode\Exception\InvalidArgumentException;
  7 +use DASPRiD\Enum\AbstractEnum;
  8 +
  9 +/**
  10 + * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004.
  11 + *
  12 + * @method static self CP437()
  13 + * @method static self ISO8859_1()
  14 + * @method static self ISO8859_2()
  15 + * @method static self ISO8859_3()
  16 + * @method static self ISO8859_4()
  17 + * @method static self ISO8859_5()
  18 + * @method static self ISO8859_6()
  19 + * @method static self ISO8859_7()
  20 + * @method static self ISO8859_8()
  21 + * @method static self ISO8859_9()
  22 + * @method static self ISO8859_10()
  23 + * @method static self ISO8859_11()
  24 + * @method static self ISO8859_12()
  25 + * @method static self ISO8859_13()
  26 + * @method static self ISO8859_14()
  27 + * @method static self ISO8859_15()
  28 + * @method static self ISO8859_16()
  29 + * @method static self SJIS()
  30 + * @method static self CP1250()
  31 + * @method static self CP1251()
  32 + * @method static self CP1252()
  33 + * @method static self CP1256()
  34 + * @method static self UNICODE_BIG_UNMARKED()
  35 + * @method static self UTF8()
  36 + * @method static self ASCII()
  37 + * @method static self BIG5()
  38 + * @method static self GB18030()
  39 + * @method static self EUC_KR()
  40 + */
  41 +final class CharacterSetEci extends AbstractEnum
  42 +{
  43 + protected const CP437 = [[0, 2]];
  44 + protected const ISO8859_1 = [[1, 3], 'ISO-8859-1'];
  45 + protected const ISO8859_2 = [[4], 'ISO-8859-2'];
  46 + protected const ISO8859_3 = [[5], 'ISO-8859-3'];
  47 + protected const ISO8859_4 = [[6], 'ISO-8859-4'];
  48 + protected const ISO8859_5 = [[7], 'ISO-8859-5'];
  49 + protected const ISO8859_6 = [[8], 'ISO-8859-6'];
  50 + protected const ISO8859_7 = [[9], 'ISO-8859-7'];
  51 + protected const ISO8859_8 = [[10], 'ISO-8859-8'];
  52 + protected const ISO8859_9 = [[11], 'ISO-8859-9'];
  53 + protected const ISO8859_10 = [[12], 'ISO-8859-10'];
  54 + protected const ISO8859_11 = [[13], 'ISO-8859-11'];
  55 + protected const ISO8859_12 = [[14], 'ISO-8859-12'];
  56 + protected const ISO8859_13 = [[15], 'ISO-8859-13'];
  57 + protected const ISO8859_14 = [[16], 'ISO-8859-14'];
  58 + protected const ISO8859_15 = [[17], 'ISO-8859-15'];
  59 + protected const ISO8859_16 = [[18], 'ISO-8859-16'];
  60 + protected const SJIS = [[20], 'Shift_JIS'];
  61 + protected const CP1250 = [[21], 'windows-1250'];
  62 + protected const CP1251 = [[22], 'windows-1251'];
  63 + protected const CP1252 = [[23], 'windows-1252'];
  64 + protected const CP1256 = [[24], 'windows-1256'];
  65 + protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig'];
  66 + protected const UTF8 = [[26], 'UTF-8'];
  67 + protected const ASCII = [[27, 170], 'US-ASCII'];
  68 + protected const BIG5 = [[28]];
  69 + protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
  70 + protected const EUC_KR = [[30], 'EUC-KR'];
  71 +
  72 + /**
  73 + * @var int[]
  74 + */
  75 + private $values;
  76 +
  77 + /**
  78 + * @var string[]
  79 + */
  80 + private $otherEncodingNames;
  81 +
  82 + /**
  83 + * @var array<int, self>|null
  84 + */
  85 + private static $valueToEci;
  86 +
  87 + /**
  88 + * @var array<string, self>|null
  89 + */
  90 + private static $nameToEci;
  91 +
  92 + public function __construct(array $values, string ...$otherEncodingNames)
  93 + {
  94 + $this->values = $values;
  95 + $this->otherEncodingNames = $otherEncodingNames;
  96 + }
  97 +
  98 + /**
  99 + * Returns the primary value.
  100 + */
  101 + public function getValue() : int
  102 + {
  103 + return $this->values[0];
  104 + }
  105 +
  106 + /**
  107 + * Gets character set ECI by value.
  108 + *
  109 + * Returns the representing ECI of a given value, or null if it is legal but unsupported.
  110 + *
  111 + * @throws InvalidArgumentException if value is not between 0 and 900
  112 + */
  113 + public static function getCharacterSetEciByValue(int $value) : ?self
  114 + {
  115 + if ($value < 0 || $value >= 900) {
  116 + throw new InvalidArgumentException('Value must be between 0 and 900');
  117 + }
  118 +
  119 + $valueToEci = self::valueToEci();
  120 +
  121 + if (! array_key_exists($value, $valueToEci)) {
  122 + return null;
  123 + }
  124 +
  125 + return $valueToEci[$value];
  126 + }
  127 +
  128 + /**
  129 + * Returns character set ECI by name.
  130 + *
  131 + * Returns the representing ECI of a given name, or null if it is legal but unsupported
  132 + */
  133 + public static function getCharacterSetEciByName(string $name) : ?self
  134 + {
  135 + $nameToEci = self::nameToEci();
  136 + $name = strtolower($name);
  137 +
  138 + if (! array_key_exists($name, $nameToEci)) {
  139 + return null;
  140 + }
  141 +
  142 + return $nameToEci[$name];
  143 + }
  144 +
  145 + private static function valueToEci() : array
  146 + {
  147 + if (null !== self::$valueToEci) {
  148 + return self::$valueToEci;
  149 + }
  150 +
  151 + self::$valueToEci = [];
  152 +
  153 + foreach (self::values() as $eci) {
  154 + foreach ($eci->values as $value) {
  155 + self::$valueToEci[$value] = $eci;
  156 + }
  157 + }
  158 +
  159 + return self::$valueToEci;
  160 + }
  161 +
  162 + private static function nameToEci() : array
  163 + {
  164 + if (null !== self::$nameToEci) {
  165 + return self::$nameToEci;
  166 + }
  167 +
  168 + self::$nameToEci = [];
  169 +
  170 + foreach (self::values() as $eci) {
  171 + self::$nameToEci[strtolower($eci->name())] = $eci;
  172 +
  173 + foreach ($eci->otherEncodingNames as $name) {
  174 + self::$nameToEci[strtolower($name)] = $eci;
  175 + }
  176 + }
  177 +
  178 + return self::$nameToEci;
  179 + }
  180 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +/**
  7 + * Encapsulates the parameters for one error-correction block in one symbol version.
  8 + *
  9 + * This includes the number of data codewords, and the number of times a block with these parameters is used
  10 + * consecutively in the QR code version's format.
  11 + */
  12 +final class EcBlock
  13 +{
  14 + /**
  15 + * How many times the block is used.
  16 + *
  17 + * @var int
  18 + */
  19 + private $count;
  20 +
  21 + /**
  22 + * Number of data codewords.
  23 + *
  24 + * @var int
  25 + */
  26 + private $dataCodewords;
  27 +
  28 + public function __construct(int $count, int $dataCodewords)
  29 + {
  30 + $this->count = $count;
  31 + $this->dataCodewords = $dataCodewords;
  32 + }
  33 +
  34 + /**
  35 + * Returns how many times the block is used.
  36 + */
  37 + public function getCount() : int
  38 + {
  39 + return $this->count;
  40 + }
  41 +
  42 + /**
  43 + * Returns the number of data codewords.
  44 + */
  45 + public function getDataCodewords() : int
  46 + {
  47 + return $this->dataCodewords;
  48 + }
  49 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +/**
  7 + * Encapsulates a set of error-correction blocks in one symbol version.
  8 + *
  9 + * Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each
  10 + * set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all
  11 + * blocks within one version.
  12 + */
  13 +final class EcBlocks
  14 +{
  15 + /**
  16 + * Number of EC codewords per block.
  17 + *
  18 + * @var int
  19 + */
  20 + private $ecCodewordsPerBlock;
  21 +
  22 + /**
  23 + * List of EC blocks.
  24 + *
  25 + * @var EcBlock[]
  26 + */
  27 + private $ecBlocks;
  28 +
  29 + public function __construct(int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
  30 + {
  31 + $this->ecCodewordsPerBlock = $ecCodewordsPerBlock;
  32 + $this->ecBlocks = $ecBlocks;
  33 + }
  34 +
  35 + /**
  36 + * Returns the number of EC codewords per block.
  37 + */
  38 + public function getEcCodewordsPerBlock() : int
  39 + {
  40 + return $this->ecCodewordsPerBlock;
  41 + }
  42 +
  43 + /**
  44 + * Returns the total number of EC block appearances.
  45 + */
  46 + public function getNumBlocks() : int
  47 + {
  48 + $total = 0;
  49 +
  50 + foreach ($this->ecBlocks as $ecBlock) {
  51 + $total += $ecBlock->getCount();
  52 + }
  53 +
  54 + return $total;
  55 + }
  56 +
  57 + /**
  58 + * Returns the total count of EC codewords.
  59 + */
  60 + public function getTotalEcCodewords() : int
  61 + {
  62 + return $this->ecCodewordsPerBlock * $this->getNumBlocks();
  63 + }
  64 +
  65 + /**
  66 + * Returns the EC blocks included in this collection.
  67 + *
  68 + * @return EcBlock[]
  69 + */
  70 + public function getEcBlocks() : array
  71 + {
  72 + return $this->ecBlocks;
  73 + }
  74 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use BaconQrCode\Exception\OutOfBoundsException;
  7 +use DASPRiD\Enum\AbstractEnum;
  8 +
  9 +/**
  10 + * Enum representing the four error correction levels.
  11 + *
  12 + * @method static self L() ~7% correction
  13 + * @method static self M() ~15% correction
  14 + * @method static self Q() ~25% correction
  15 + * @method static self H() ~30% correction
  16 + */
  17 +final class ErrorCorrectionLevel extends AbstractEnum
  18 +{
  19 + protected const L = [0x01];
  20 + protected const M = [0x00];
  21 + protected const Q = [0x03];
  22 + protected const H = [0x02];
  23 +
  24 + /**
  25 + * @var int
  26 + */
  27 + private $bits;
  28 +
  29 + protected function __construct(int $bits)
  30 + {
  31 + $this->bits = $bits;
  32 + }
  33 +
  34 + /**
  35 + * @throws OutOfBoundsException if number of bits is invalid
  36 + */
  37 + public static function forBits(int $bits) : self
  38 + {
  39 + switch ($bits) {
  40 + case 0:
  41 + return self::M();
  42 +
  43 + case 1:
  44 + return self::L();
  45 +
  46 + case 2:
  47 + return self::H();
  48 +
  49 + case 3:
  50 + return self::Q();
  51 + }
  52 +
  53 + throw new OutOfBoundsException('Invalid number of bits');
  54 + }
  55 +
  56 + /**
  57 + * Returns the two bits used to encode this error correction level.
  58 + */
  59 + public function getBits() : int
  60 + {
  61 + return $this->bits;
  62 + }
  63 +}
  1 +<?php
  2 +/**
  3 + * BaconQrCode
  4 + *
  5 + * @link http://github.com/Bacon/BaconQrCode For the canonical source repository
  6 + * @copyright 2013 Ben 'DASPRiD' Scholzen
  7 + * @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
  8 + */
  9 +
  10 +namespace BaconQrCode\Common;
  11 +
  12 +/**
  13 + * Encapsulates a QR Code's format information, including the data mask used and error correction level.
  14 + */
  15 +class FormatInformation
  16 +{
  17 + /**
  18 + * Mask for format information.
  19 + */
  20 + private const FORMAT_INFO_MASK_QR = 0x5412;
  21 +
  22 + /**
  23 + * Lookup table for decoding format information.
  24 + *
  25 + * See ISO 18004:2006, Annex C, Table C.1
  26 + */
  27 + private const FORMAT_INFO_DECODE_LOOKUP = [
  28 + [0x5412, 0x00],
  29 + [0x5125, 0x01],
  30 + [0x5e7c, 0x02],
  31 + [0x5b4b, 0x03],
  32 + [0x45f9, 0x04],
  33 + [0x40ce, 0x05],
  34 + [0x4f97, 0x06],
  35 + [0x4aa0, 0x07],
  36 + [0x77c4, 0x08],
  37 + [0x72f3, 0x09],
  38 + [0x7daa, 0x0a],
  39 + [0x789d, 0x0b],
  40 + [0x662f, 0x0c],
  41 + [0x6318, 0x0d],
  42 + [0x6c41, 0x0e],
  43 + [0x6976, 0x0f],
  44 + [0x1689, 0x10],
  45 + [0x13be, 0x11],
  46 + [0x1ce7, 0x12],
  47 + [0x19d0, 0x13],
  48 + [0x0762, 0x14],
  49 + [0x0255, 0x15],
  50 + [0x0d0c, 0x16],
  51 + [0x083b, 0x17],
  52 + [0x355f, 0x18],
  53 + [0x3068, 0x19],
  54 + [0x3f31, 0x1a],
  55 + [0x3a06, 0x1b],
  56 + [0x24b4, 0x1c],
  57 + [0x2183, 0x1d],
  58 + [0x2eda, 0x1e],
  59 + [0x2bed, 0x1f],
  60 + ];
  61 +
  62 + /**
  63 + * Offset i holds the number of 1 bits in the binary representation of i.
  64 + *
  65 + * @var array
  66 + */
  67 + private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
  68 +
  69 + /**
  70 + * Error correction level.
  71 + *
  72 + * @var ErrorCorrectionLevel
  73 + */
  74 + private $ecLevel;
  75 +
  76 + /**
  77 + * Data mask.
  78 + *
  79 + * @var int
  80 + */
  81 + private $dataMask;
  82 +
  83 + protected function __construct(int $formatInfo)
  84 + {
  85 + $this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3);
  86 + $this->dataMask = $formatInfo & 0x7;
  87 + }
  88 +
  89 + /**
  90 + * Checks how many bits are different between two integers.
  91 + */
  92 + public static function numBitsDiffering(int $a, int $b) : int
  93 + {
  94 + $a ^= $b;
  95 +
  96 + return (
  97 + self::BITS_SET_IN_HALF_BYTE[$a & 0xf]
  98 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)]
  99 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)]
  100 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)]
  101 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)]
  102 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)]
  103 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)]
  104 + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)]
  105 + );
  106 + }
  107 +
  108 + /**
  109 + * Decodes format information.
  110 + */
  111 + public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
  112 + {
  113 + $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
  114 +
  115 + if (null !== $formatInfo) {
  116 + return $formatInfo;
  117 + }
  118 +
  119 + // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the
  120 + // pattern first.
  121 + return self::doDecodeFormatInformation(
  122 + $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR,
  123 + $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR
  124 + );
  125 + }
  126 +
  127 + /**
  128 + * Internal method for decoding format information.
  129 + */
  130 + private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
  131 + {
  132 + $bestDifference = PHP_INT_MAX;
  133 + $bestFormatInfo = 0;
  134 +
  135 + foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) {
  136 + $targetInfo = $decodeInfo[0];
  137 +
  138 + if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) {
  139 + // Found an exact match
  140 + return new self($decodeInfo[1]);
  141 + }
  142 +
  143 + $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
  144 +
  145 + if ($bitsDifference < $bestDifference) {
  146 + $bestFormatInfo = $decodeInfo[1];
  147 + $bestDifference = $bitsDifference;
  148 + }
  149 +
  150 + if ($maskedFormatInfo1 !== $maskedFormatInfo2) {
  151 + // Also try the other option
  152 + $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
  153 +
  154 + if ($bitsDifference < $bestDifference) {
  155 + $bestFormatInfo = $decodeInfo[1];
  156 + $bestDifference = $bitsDifference;
  157 + }
  158 + }
  159 + }
  160 +
  161 + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match.
  162 + if ($bestDifference <= 3) {
  163 + return new self($bestFormatInfo);
  164 + }
  165 +
  166 + return null;
  167 + }
  168 +
  169 + /**
  170 + * Returns the error correction level.
  171 + */
  172 + public function getErrorCorrectionLevel() : ErrorCorrectionLevel
  173 + {
  174 + return $this->ecLevel;
  175 + }
  176 +
  177 + /**
  178 + * Returns the data mask.
  179 + */
  180 + public function getDataMask() : int
  181 + {
  182 + return $this->dataMask;
  183 + }
  184 +
  185 + /**
  186 + * Hashes the code of the EC level.
  187 + */
  188 + public function hashCode() : int
  189 + {
  190 + return ($this->ecLevel->getBits() << 3) | $this->dataMask;
  191 + }
  192 +
  193 + /**
  194 + * Verifies if this instance equals another one.
  195 + */
  196 + public function equals(self $other) : bool
  197 + {
  198 + return (
  199 + $this->ecLevel === $other->ecLevel
  200 + && $this->dataMask === $other->dataMask
  201 + );
  202 + }
  203 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use DASPRiD\Enum\AbstractEnum;
  7 +
  8 +/**
  9 + * Enum representing various modes in which data can be encoded to bits.
  10 + *
  11 + * @method static self TERMINATOR()
  12 + * @method static self NUMERIC()
  13 + * @method static self ALPHANUMERIC()
  14 + * @method static self STRUCTURED_APPEND()
  15 + * @method static self BYTE()
  16 + * @method static self ECI()
  17 + * @method static self KANJI()
  18 + * @method static self FNC1_FIRST_POSITION()
  19 + * @method static self FNC1_SECOND_POSITION()
  20 + * @method static self HANZI()
  21 + */
  22 +final class Mode extends AbstractEnum
  23 +{
  24 + protected const TERMINATOR = [[0, 0, 0], 0x00];
  25 + protected const NUMERIC = [[10, 12, 14], 0x01];
  26 + protected const ALPHANUMERIC = [[9, 11, 13], 0x02];
  27 + protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03];
  28 + protected const BYTE = [[8, 16, 16], 0x04];
  29 + protected const ECI = [[0, 0, 0], 0x07];
  30 + protected const KANJI = [[8, 10, 12], 0x08];
  31 + protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05];
  32 + protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09];
  33 + protected const HANZI = [[8, 10, 12], 0x0d];
  34 +
  35 + /**
  36 + * @var int[]
  37 + */
  38 + private $characterCountBitsForVersions;
  39 +
  40 + /**
  41 + * @var int
  42 + */
  43 + private $bits;
  44 +
  45 + protected function __construct(array $characterCountBitsForVersions, int $bits)
  46 + {
  47 + $this->characterCountBitsForVersions = $characterCountBitsForVersions;
  48 + $this->bits = $bits;
  49 + }
  50 +
  51 + /**
  52 + * Returns the number of bits used in a specific QR code version.
  53 + */
  54 + public function getCharacterCountBits(Version $version) : int
  55 + {
  56 + $number = $version->getVersionNumber();
  57 +
  58 + if ($number <= 9) {
  59 + $offset = 0;
  60 + } elseif ($number <= 26) {
  61 + $offset = 1;
  62 + } else {
  63 + $offset = 2;
  64 + }
  65 +
  66 + return $this->characterCountBitsForVersions[$offset];
  67 + }
  68 +
  69 + /**
  70 + * Returns the four bits used to encode this mode.
  71 + */
  72 + public function getBits() : int
  73 + {
  74 + return $this->bits;
  75 + }
  76 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use BaconQrCode\Exception\InvalidArgumentException;
  7 +use BaconQrCode\Exception\RuntimeException;
  8 +use SplFixedArray;
  9 +
  10 +/**
  11 + * Reed-Solomon codec for 8-bit characters.
  12 + *
  13 + * Based on libfec by Phil Karn, KA9Q.
  14 + */
  15 +final class ReedSolomonCodec
  16 +{
  17 + /**
  18 + * Symbol size in bits.
  19 + *
  20 + * @var int
  21 + */
  22 + private $symbolSize;
  23 +
  24 + /**
  25 + * Block size in symbols.
  26 + *
  27 + * @var int
  28 + */
  29 + private $blockSize;
  30 +
  31 + /**
  32 + * First root of RS code generator polynomial, index form.
  33 + *
  34 + * @var int
  35 + */
  36 + private $firstRoot;
  37 +
  38 + /**
  39 + * Primitive element to generate polynomial roots, index form.
  40 + *
  41 + * @var int
  42 + */
  43 + private $primitive;
  44 +
  45 + /**
  46 + * Prim-th root of 1, index form.
  47 + *
  48 + * @var int
  49 + */
  50 + private $iPrimitive;
  51 +
  52 + /**
  53 + * RS code generator polynomial degree (number of roots).
  54 + *
  55 + * @var int
  56 + */
  57 + private $numRoots;
  58 +
  59 + /**
  60 + * Padding bytes at front of shortened block.
  61 + *
  62 + * @var int
  63 + */
  64 + private $padding;
  65 +
  66 + /**
  67 + * Log lookup table.
  68 + *
  69 + * @var SplFixedArray
  70 + */
  71 + private $alphaTo;
  72 +
  73 + /**
  74 + * Anti-Log lookup table.
  75 + *
  76 + * @var SplFixedArray
  77 + */
  78 + private $indexOf;
  79 +
  80 + /**
  81 + * Generator polynomial.
  82 + *
  83 + * @var SplFixedArray
  84 + */
  85 + private $generatorPoly;
  86 +
  87 + /**
  88 + * @throws InvalidArgumentException if symbol size ist not between 0 and 8
  89 + * @throws InvalidArgumentException if first root is invalid
  90 + * @throws InvalidArgumentException if num roots is invalid
  91 + * @throws InvalidArgumentException if padding is invalid
  92 + * @throws RuntimeException if field generator polynomial is not primitive
  93 + */
  94 + public function __construct(
  95 + int $symbolSize,
  96 + int $gfPoly,
  97 + int $firstRoot,
  98 + int $primitive,
  99 + int $numRoots,
  100 + int $padding
  101 + ) {
  102 + if ($symbolSize < 0 || $symbolSize > 8) {
  103 + throw new InvalidArgumentException('Symbol size must be between 0 and 8');
  104 + }
  105 +
  106 + if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
  107 + throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
  108 + }
  109 +
  110 + if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
  111 + throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
  112 + }
  113 +
  114 + if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
  115 + throw new InvalidArgumentException(
  116 + 'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)
  117 + );
  118 + }
  119 +
  120 + $this->symbolSize = $symbolSize;
  121 + $this->blockSize = (1 << $symbolSize) - 1;
  122 + $this->padding = $padding;
  123 + $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
  124 + $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
  125 +
  126 + // Generate galous field lookup table
  127 + $this->indexOf[0] = $this->blockSize;
  128 + $this->alphaTo[$this->blockSize] = 0;
  129 +
  130 + $sr = 1;
  131 +
  132 + for ($i = 0; $i < $this->blockSize; ++$i) {
  133 + $this->indexOf[$sr] = $i;
  134 + $this->alphaTo[$i] = $sr;
  135 +
  136 + $sr <<= 1;
  137 +
  138 + if ($sr & (1 << $symbolSize)) {
  139 + $sr ^= $gfPoly;
  140 + }
  141 +
  142 + $sr &= $this->blockSize;
  143 + }
  144 +
  145 + if (1 !== $sr) {
  146 + throw new RuntimeException('Field generator polynomial is not primitive');
  147 + }
  148 +
  149 + // Form RS code generator polynomial from its roots
  150 + $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
  151 + $this->firstRoot = $firstRoot;
  152 + $this->primitive = $primitive;
  153 + $this->numRoots = $numRoots;
  154 +
  155 + // Find prim-th root of 1, used in decoding
  156 + for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) {
  157 + }
  158 +
  159 + $this->iPrimitive = intdiv($iPrimitive, $primitive);
  160 +
  161 + $this->generatorPoly[0] = 1;
  162 +
  163 + for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) {
  164 + $this->generatorPoly[$i + 1] = 1;
  165 +
  166 + for ($j = $i; $j > 0; $j--) {
  167 + if ($this->generatorPoly[$j] !== 0) {
  168 + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[
  169 + $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)
  170 + ];
  171 + } else {
  172 + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
  173 + }
  174 + }
  175 +
  176 + $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
  177 + }
  178 +
  179 + // Convert generator poly to index form for quicker encoding
  180 + for ($i = 0; $i <= $numRoots; ++$i) {
  181 + $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
  182 + }
  183 + }
  184 +
  185 + /**
  186 + * Encodes data and writes result back into parity array.
  187 + */
  188 + public function encode(SplFixedArray $data, SplFixedArray $parity) : void
  189 + {
  190 + for ($i = 0; $i < $this->numRoots; ++$i) {
  191 + $parity[$i] = 0;
  192 + }
  193 +
  194 + $iterations = $this->blockSize - $this->numRoots - $this->padding;
  195 +
  196 + for ($i = 0; $i < $iterations; ++$i) {
  197 + $feedback = $this->indexOf[$data[$i] ^ $parity[0]];
  198 +
  199 + if ($feedback !== $this->blockSize) {
  200 + // Feedback term is non-zero
  201 + $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
  202 +
  203 + for ($j = 1; $j < $this->numRoots; ++$j) {
  204 + $parity[$j] = $parity[$j] ^ $this->alphaTo[
  205 + $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])
  206 + ];
  207 + }
  208 + }
  209 +
  210 + for ($j = 0; $j < $this->numRoots - 1; ++$j) {
  211 + $parity[$j] = $parity[$j + 1];
  212 + }
  213 +
  214 + if ($feedback !== $this->blockSize) {
  215 + $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
  216 + } else {
  217 + $parity[$this->numRoots - 1] = 0;
  218 + }
  219 + }
  220 + }
  221 +
  222 + /**
  223 + * Decodes received data.
  224 + */
  225 + public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int
  226 + {
  227 + // This speeds up the initialization a bit.
  228 + $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
  229 + $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
  230 +
  231 + $lambda = clone $numRootsPlusOne;
  232 + $b = clone $numRootsPlusOne;
  233 + $t = clone $numRootsPlusOne;
  234 + $omega = clone $numRootsPlusOne;
  235 + $root = clone $numRoots;
  236 + $loc = clone $numRoots;
  237 +
  238 + $numErasures = (null !== $erasures ? count($erasures) : 0);
  239 +
  240 + // Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
  241 + $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
  242 +
  243 + for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) {
  244 + for ($j = 0; $j < $this->numRoots; ++$j) {
  245 + if ($syndromes[$j] === 0) {
  246 + $syndromes[$j] = $data[$i];
  247 + } else {
  248 + $syndromes[$j] = $data[$i] ^ $this->alphaTo[
  249 + $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
  250 + ];
  251 + }
  252 + }
  253 + }
  254 +
  255 + // Convert syndromes to index form, checking for nonzero conditions
  256 + $syndromeError = 0;
  257 +
  258 + for ($i = 0; $i < $this->numRoots; ++$i) {
  259 + $syndromeError |= $syndromes[$i];
  260 + $syndromes[$i] = $this->indexOf[$syndromes[$i]];
  261 + }
  262 +
  263 + if (! $syndromeError) {
  264 + // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[]
  265 + // unmodified.
  266 + return 0;
  267 + }
  268 +
  269 + $lambda[0] = 1;
  270 +
  271 + if ($numErasures > 0) {
  272 + // Init lambda to be the erasure locator polynomial
  273 + $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
  274 +
  275 + for ($i = 1; $i < $numErasures; ++$i) {
  276 + $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
  277 +
  278 + for ($j = $i + 1; $j > 0; --$j) {
  279 + $tmp = $this->indexOf[$lambda[$j - 1]];
  280 +
  281 + if ($tmp !== $this->blockSize) {
  282 + $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
  283 + }
  284 + }
  285 + }
  286 + }
  287 +
  288 + for ($i = 0; $i <= $this->numRoots; ++$i) {
  289 + $b[$i] = $this->indexOf[$lambda[$i]];
  290 + }
  291 +
  292 + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
  293 + $r = $numErasures;
  294 + $el = $numErasures;
  295 +
  296 + while (++$r <= $this->numRoots) {
  297 + // Compute discrepancy at the r-th step in poly form
  298 + $discrepancyR = 0;
  299 +
  300 + for ($i = 0; $i < $r; ++$i) {
  301 + if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
  302 + $discrepancyR ^= $this->alphaTo[
  303 + $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])
  304 + ];
  305 + }
  306 + }
  307 +
  308 + $discrepancyR = $this->indexOf[$discrepancyR];
  309 +
  310 + if ($discrepancyR === $this->blockSize) {
  311 + $tmp = $b->toArray();
  312 + array_unshift($tmp, $this->blockSize);
  313 + array_pop($tmp);
  314 + $b = SplFixedArray::fromArray($tmp, false);
  315 + continue;
  316 + }
  317 +
  318 + $t[0] = $lambda[0];
  319 +
  320 + for ($i = 0; $i < $this->numRoots; ++$i) {
  321 + if ($b[$i] !== $this->blockSize) {
  322 + $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
  323 + } else {
  324 + $t[$i + 1] = $lambda[$i + 1];
  325 + }
  326 + }
  327 +
  328 + if (2 * $el <= $r + $numErasures - 1) {
  329 + $el = $r + $numErasures - $el;
  330 +
  331 + for ($i = 0; $i <= $this->numRoots; ++$i) {
  332 + $b[$i] = (
  333 + $lambda[$i] === 0
  334 + ? $this->blockSize
  335 + : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
  336 + );
  337 + }
  338 + } else {
  339 + $tmp = $b->toArray();
  340 + array_unshift($tmp, $this->blockSize);
  341 + array_pop($tmp);
  342 + $b = SplFixedArray::fromArray($tmp, false);
  343 + }
  344 +
  345 + $lambda = clone $t;
  346 + }
  347 +
  348 + // Convert lambda to index form and compute deg(lambda(x))
  349 + $degLambda = 0;
  350 +
  351 + for ($i = 0; $i <= $this->numRoots; ++$i) {
  352 + $lambda[$i] = $this->indexOf[$lambda[$i]];
  353 +
  354 + if ($lambda[$i] !== $this->blockSize) {
  355 + $degLambda = $i;
  356 + }
  357 + }
  358 +
  359 + // Find roots of the error+erasure locator polynomial by Chien search.
  360 + $reg = clone $lambda;
  361 + $reg[0] = 0;
  362 + $count = 0;
  363 + $i = 1;
  364 +
  365 + for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) {
  366 + $q = 1;
  367 +
  368 + for ($j = $degLambda; $j > 0; $j--) {
  369 + if ($reg[$j] !== $this->blockSize) {
  370 + $reg[$j] = $this->modNn($reg[$j] + $j);
  371 + $q ^= $this->alphaTo[$reg[$j]];
  372 + }
  373 + }
  374 +
  375 + if ($q !== 0) {
  376 + // Not a root
  377 + continue;
  378 + }
  379 +
  380 + // Store root (index-form) and error location number
  381 + $root[$count] = $i;
  382 + $loc[$count] = $k;
  383 +
  384 + if (++$count === $degLambda) {
  385 + break;
  386 + }
  387 + }
  388 +
  389 + if ($degLambda !== $count) {
  390 + // deg(lambda) unequal to number of roots: uncorrectable error detected
  391 + return null;
  392 + }
  393 +
  394 + // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find
  395 + // deg(omega).
  396 + $degOmega = $degLambda - 1;
  397 +
  398 + for ($i = 0; $i <= $degOmega; ++$i) {
  399 + $tmp = 0;
  400 +
  401 + for ($j = $i; $j >= 0; --$j) {
  402 + if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
  403 + $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
  404 + }
  405 + }
  406 +
  407 + $omega[$i] = $this->indexOf[$tmp];
  408 + }
  409 +
  410 + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and
  411 + // den = lambda_pr(inv(X(l))) all in poly form.
  412 + for ($j = $count - 1; $j >= 0; --$j) {
  413 + $num1 = 0;
  414 +
  415 + for ($i = $degOmega; $i >= 0; $i--) {
  416 + if ($omega[$i] !== $this->blockSize) {
  417 + $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
  418 + }
  419 + }
  420 +
  421 + $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
  422 + $den = 0;
  423 +
  424 + // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i]
  425 + for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
  426 + if ($lambda[$i + 1] !== $this->blockSize) {
  427 + $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
  428 + }
  429 + }
  430 +
  431 + // Apply error to data
  432 + if ($num1 !== 0 && $loc[$j] >= $this->padding) {
  433 + $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
  434 + $this->alphaTo[
  435 + $this->modNn(
  436 + $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
  437 + )
  438 + ]
  439 + );
  440 + }
  441 + }
  442 +
  443 + if (null !== $erasures) {
  444 + if (count($erasures) < $count) {
  445 + $erasures->setSize($count);
  446 + }
  447 +
  448 + for ($i = 0; $i < $count; $i++) {
  449 + $erasures[$i] = $loc[$i];
  450 + }
  451 + }
  452 +
  453 + return $count;
  454 + }
  455 +
  456 + /**
  457 + * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide.
  458 + */
  459 + private function modNn(int $x) : int
  460 + {
  461 + while ($x >= $this->blockSize) {
  462 + $x -= $this->blockSize;
  463 + $x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
  464 + }
  465 +
  466 + return $x;
  467 + }
  468 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Common;
  5 +
  6 +use BaconQrCode\Exception\InvalidArgumentException;
  7 +use SplFixedArray;
  8 +
  9 +/**
  10 + * Version representation.
  11 + */
  12 +final class Version
  13 +{
  14 + private const VERSION_DECODE_INFO = [
  15 + 0x07c94,
  16 + 0x085bc,
  17 + 0x09a99,
  18 + 0x0a4d3,
  19 + 0x0bbf6,
  20 + 0x0c762,
  21 + 0x0d847,
  22 + 0x0e60d,
  23 + 0x0f928,
  24 + 0x10b78,
  25 + 0x1145d,
  26 + 0x12a17,
  27 + 0x13532,
  28 + 0x149a6,
  29 + 0x15683,
  30 + 0x168c9,
  31 + 0x177ec,
  32 + 0x18ec4,
  33 + 0x191e1,
  34 + 0x1afab,
  35 + 0x1b08e,
  36 + 0x1cc1a,
  37 + 0x1d33f,
  38 + 0x1ed75,
  39 + 0x1f250,
  40 + 0x209d5,
  41 + 0x216f0,
  42 + 0x228ba,
  43 + 0x2379f,
  44 + 0x24b0b,
  45 + 0x2542e,
  46 + 0x26a64,
  47 + 0x27541,
  48 + 0x28c69,
  49 + ];
  50 +
  51 + /**
  52 + * Version number of this version.
  53 + *
  54 + * @var int
  55 + */
  56 + private $versionNumber;
  57 +
  58 + /**
  59 + * Alignment pattern centers.
  60 + *
  61 + * @var SplFixedArray
  62 + */
  63 + private $alignmentPatternCenters;
  64 +
  65 + /**
  66 + * Error correction blocks.
  67 + *
  68 + * @var EcBlocks[]
  69 + */
  70 + private $ecBlocks;
  71 +
  72 + /**
  73 + * Total number of codewords.
  74 + *
  75 + * @var int
  76 + */
  77 + private $totalCodewords;
  78 +
  79 + /**
  80 + * Cached version instances.
  81 + *
  82 + * @var array<int, self>|null
  83 + */
  84 + private static $versions;
  85 +
  86 + /**
  87 + * @param int[] $alignmentPatternCenters
  88 + */
  89 + private function __construct(
  90 + int $versionNumber,
  91 + array $alignmentPatternCenters,
  92 + EcBlocks ...$ecBlocks
  93 + ) {
  94 + $this->versionNumber = $versionNumber;
  95 + $this->alignmentPatternCenters = $alignmentPatternCenters;
  96 + $this->ecBlocks = $ecBlocks;
  97 +
  98 + $totalCodewords = 0;
  99 + $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock();
  100 +
  101 + foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) {
  102 + $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords);
  103 + }
  104 +
  105 + $this->totalCodewords = $totalCodewords;
  106 + }
  107 +
  108 + /**
  109 + * Returns the version number.
  110 + */
  111 + public function getVersionNumber() : int
  112 + {
  113 + return $this->versionNumber;
  114 + }
  115 +
  116 + /**
  117 + * Returns the alignment pattern centers.
  118 + *
  119 + * @return int[]
  120 + */
  121 + public function getAlignmentPatternCenters() : array
  122 + {
  123 + return $this->alignmentPatternCenters;
  124 + }
  125 +
  126 + /**
  127 + * Returns the total number of codewords.
  128 + */
  129 + public function getTotalCodewords() : int
  130 + {
  131 + return $this->totalCodewords;
  132 + }
  133 +
  134 + /**
  135 + * Calculates the dimension for the current version.
  136 + */
  137 + public function getDimensionForVersion() : int
  138 + {
  139 + return 17 + 4 * $this->versionNumber;
  140 + }
  141 +
  142 + /**
  143 + * Returns the number of EC blocks for a specific EC level.
  144 + */
  145 + public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks
  146 + {
  147 + return $this->ecBlocks[$ecLevel->ordinal()];
  148 + }
  149 +
  150 + /**
  151 + * Gets a provisional version number for a specific dimension.
  152 + *
  153 + * @throws InvalidArgumentException if dimension is not 1 mod 4
  154 + */
  155 + public static function getProvisionalVersionForDimension(int $dimension) : self
  156 + {
  157 + if (1 !== $dimension % 4) {
  158 + throw new InvalidArgumentException('Dimension is not 1 mod 4');
  159 + }
  160 +
  161 + return self::getVersionForNumber(intdiv($dimension - 17, 4));
  162 + }
  163 +
  164 + /**
  165 + * Gets a version instance for a specific version number.
  166 + *
  167 + * @throws InvalidArgumentException if version number is out of range
  168 + */
  169 + public static function getVersionForNumber(int $versionNumber) : self
  170 + {
  171 + if ($versionNumber < 1 || $versionNumber > 40) {
  172 + throw new InvalidArgumentException('Version number must be between 1 and 40');
  173 + }
  174 +
  175 + return self::versions()[$versionNumber - 1];
  176 + }
  177 +
  178 + /**
  179 + * Decodes version information from an integer and returns the version.
  180 + */
  181 + public static function decodeVersionInformation(int $versionBits) : ?self
  182 + {
  183 + $bestDifference = PHP_INT_MAX;
  184 + $bestVersion = 0;
  185 +
  186 + foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) {
  187 + if ($targetVersion === $versionBits) {
  188 + return self::getVersionForNumber($i + 7);
  189 + }
  190 +
  191 + $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion);
  192 +
  193 + if ($bitsDifference < $bestDifference) {
  194 + $bestVersion = $i + 7;
  195 + $bestDifference = $bitsDifference;
  196 + }
  197 + }
  198 +
  199 + if ($bestDifference <= 3) {
  200 + return self::getVersionForNumber($bestVersion);
  201 + }
  202 +
  203 + return null;
  204 + }
  205 +
  206 + /**
  207 + * Builds the function pattern for the current version.
  208 + */
  209 + public function buildFunctionPattern() : BitMatrix
  210 + {
  211 + $dimension = $this->getDimensionForVersion();
  212 + $bitMatrix = new BitMatrix($dimension);
  213 +
  214 + // Top left finder pattern + separator + format
  215 + $bitMatrix->setRegion(0, 0, 9, 9);
  216 + // Top right finder pattern + separator + format
  217 + $bitMatrix->setRegion($dimension - 8, 0, 8, 9);
  218 + // Bottom left finder pattern + separator + format
  219 + $bitMatrix->setRegion(0, $dimension - 8, 9, 8);
  220 +
  221 + // Alignment patterns
  222 + $max = count($this->alignmentPatternCenters);
  223 +
  224 + for ($x = 0; $x < $max; ++$x) {
  225 + $i = $this->alignmentPatternCenters[$x] - 2;
  226 +
  227 + for ($y = 0; $y < $max; ++$y) {
  228 + if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) {
  229 + // No alignment patterns near the three finder paterns
  230 + continue;
  231 + }
  232 +
  233 + $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5);
  234 + }
  235 + }
  236 +
  237 + // Vertical timing pattern
  238 + $bitMatrix->setRegion(6, 9, 1, $dimension - 17);
  239 + // Horizontal timing pattern
  240 + $bitMatrix->setRegion(9, 6, $dimension - 17, 1);
  241 +
  242 + if ($this->versionNumber > 6) {
  243 + // Version info, top right
  244 + $bitMatrix->setRegion($dimension - 11, 0, 3, 6);
  245 + // Version info, bottom left
  246 + $bitMatrix->setRegion(0, $dimension - 11, 6, 3);
  247 + }
  248 +
  249 + return $bitMatrix;
  250 + }
  251 +
  252 + /**
  253 + * Returns a string representation for the version.
  254 + */
  255 + public function __toString() : string
  256 + {
  257 + return (string) $this->versionNumber;
  258 + }
  259 +
  260 + /**
  261 + * Build and cache a specific version.
  262 + *
  263 + * See ISO 18004:2006 6.5.1 Table 9.
  264 + *
  265 + * @return array<int, self>
  266 + */
  267 + private static function versions() : array
  268 + {
  269 + if (null !== self::$versions) {
  270 + return self::$versions;
  271 + }
  272 +
  273 + return self::$versions = [
  274 + new self(
  275 + 1,
  276 + [],
  277 + new EcBlocks(7, new EcBlock(1, 19)),
  278 + new EcBlocks(10, new EcBlock(1, 16)),
  279 + new EcBlocks(13, new EcBlock(1, 13)),
  280 + new EcBlocks(17, new EcBlock(1, 9))
  281 + ),
  282 + new self(
  283 + 2,
  284 + [6, 18],
  285 + new EcBlocks(10, new EcBlock(1, 34)),
  286 + new EcBlocks(16, new EcBlock(1, 28)),
  287 + new EcBlocks(22, new EcBlock(1, 22)),
  288 + new EcBlocks(28, new EcBlock(1, 16))
  289 + ),
  290 + new self(
  291 + 3,
  292 + [6, 22],
  293 + new EcBlocks(15, new EcBlock(1, 55)),
  294 + new EcBlocks(26, new EcBlock(1, 44)),
  295 + new EcBlocks(18, new EcBlock(2, 17)),
  296 + new EcBlocks(22, new EcBlock(2, 13))
  297 + ),
  298 + new self(
  299 + 4,
  300 + [6, 26],
  301 + new EcBlocks(20, new EcBlock(1, 80)),
  302 + new EcBlocks(18, new EcBlock(2, 32)),
  303 + new EcBlocks(26, new EcBlock(3, 24)),
  304 + new EcBlocks(16, new EcBlock(4, 9))
  305 + ),
  306 + new self(
  307 + 5,
  308 + [6, 30],
  309 + new EcBlocks(26, new EcBlock(1, 108)),
  310 + new EcBlocks(24, new EcBlock(2, 43)),
  311 + new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)),
  312 + new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12))
  313 + ),
  314 + new self(
  315 + 6,
  316 + [6, 34],
  317 + new EcBlocks(18, new EcBlock(2, 68)),
  318 + new EcBlocks(16, new EcBlock(4, 27)),
  319 + new EcBlocks(24, new EcBlock(4, 19)),
  320 + new EcBlocks(28, new EcBlock(4, 15))
  321 + ),
  322 + new self(
  323 + 7,
  324 + [6, 22, 38],
  325 + new EcBlocks(20, new EcBlock(2, 78)),
  326 + new EcBlocks(18, new EcBlock(4, 31)),
  327 + new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)),
  328 + new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14))
  329 + ),
  330 + new self(
  331 + 8,
  332 + [6, 24, 42],
  333 + new EcBlocks(24, new EcBlock(2, 97)),
  334 + new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)),
  335 + new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)),
  336 + new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15))
  337 + ),
  338 + new self(
  339 + 9,
  340 + [6, 26, 46],
  341 + new EcBlocks(30, new EcBlock(2, 116)),
  342 + new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)),
  343 + new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)),
  344 + new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13))
  345 + ),
  346 + new self(
  347 + 10,
  348 + [6, 28, 50],
  349 + new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)),
  350 + new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)),
  351 + new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)),
  352 + new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16))
  353 + ),
  354 + new self(
  355 + 11,
  356 + [6, 30, 54],
  357 + new EcBlocks(20, new EcBlock(4, 81)),
  358 + new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)),
  359 + new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)),
  360 + new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13))
  361 + ),
  362 + new self(
  363 + 12,
  364 + [6, 32, 58],
  365 + new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)),
  366 + new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)),
  367 + new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)),
  368 + new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15))
  369 + ),
  370 + new self(
  371 + 13,
  372 + [6, 34, 62],
  373 + new EcBlocks(26, new EcBlock(4, 107)),
  374 + new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)),
  375 + new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)),
  376 + new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12))
  377 + ),
  378 + new self(
  379 + 14,
  380 + [6, 26, 46, 66],
  381 + new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)),
  382 + new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)),
  383 + new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)),
  384 + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13))
  385 + ),
  386 + new self(
  387 + 15,
  388 + [6, 26, 48, 70],
  389 + new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)),
  390 + new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)),
  391 + new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)),
  392 + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13))
  393 + ),
  394 + new self(
  395 + 16,
  396 + [6, 26, 50, 74],
  397 + new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)),
  398 + new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)),
  399 + new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)),
  400 + new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16))
  401 + ),
  402 + new self(
  403 + 17,
  404 + [6, 30, 54, 78],
  405 + new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)),
  406 + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)),
  407 + new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)),
  408 + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15))
  409 + ),
  410 + new self(
  411 + 18,
  412 + [6, 30, 56, 82],
  413 + new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)),
  414 + new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)),
  415 + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)),
  416 + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15))
  417 + ),
  418 + new self(
  419 + 19,
  420 + [6, 30, 58, 86],
  421 + new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)),
  422 + new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)),
  423 + new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)),
  424 + new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14))
  425 + ),
  426 + new self(
  427 + 20,
  428 + [6, 34, 62, 90],
  429 + new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)),
  430 + new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)),
  431 + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)),
  432 + new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16))
  433 + ),
  434 + new self(
  435 + 21,
  436 + [6, 28, 50, 72, 94],
  437 + new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)),
  438 + new EcBlocks(26, new EcBlock(17, 42)),
  439 + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)),
  440 + new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17))
  441 + ),
  442 + new self(
  443 + 22,
  444 + [6, 26, 50, 74, 98],
  445 + new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)),
  446 + new EcBlocks(28, new EcBlock(17, 46)),
  447 + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)),
  448 + new EcBlocks(24, new EcBlock(34, 13))
  449 + ),
  450 + new self(
  451 + 23,
  452 + [6, 30, 54, 78, 102],
  453 + new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)),
  454 + new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)),
  455 + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)),
  456 + new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16))
  457 + ),
  458 + new self(
  459 + 24,
  460 + [6, 28, 54, 80, 106],
  461 + new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)),
  462 + new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)),
  463 + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)),
  464 + new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17))
  465 + ),
  466 + new self(
  467 + 25,
  468 + [6, 32, 58, 84, 110],
  469 + new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)),
  470 + new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)),
  471 + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)),
  472 + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16))
  473 + ),
  474 + new self(
  475 + 26,
  476 + [6, 30, 58, 86, 114],
  477 + new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)),
  478 + new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)),
  479 + new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)),
  480 + new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17))
  481 + ),
  482 + new self(
  483 + 27,
  484 + [6, 34, 62, 90, 118],
  485 + new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)),
  486 + new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)),
  487 + new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)),
  488 + new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16))
  489 + ),
  490 + new self(
  491 + 28,
  492 + [6, 26, 50, 74, 98, 122],
  493 + new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)),
  494 + new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)),
  495 + new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)),
  496 + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16))
  497 + ),
  498 + new self(
  499 + 29,
  500 + [6, 30, 54, 78, 102, 126],
  501 + new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)),
  502 + new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)),
  503 + new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)),
  504 + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16))
  505 + ),
  506 + new self(
  507 + 30,
  508 + [6, 26, 52, 78, 104, 130],
  509 + new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)),
  510 + new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)),
  511 + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)),
  512 + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16))
  513 + ),
  514 + new self(
  515 + 31,
  516 + [6, 30, 56, 82, 108, 134],
  517 + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)),
  518 + new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)),
  519 + new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)),
  520 + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16))
  521 + ),
  522 + new self(
  523 + 32,
  524 + [6, 34, 60, 86, 112, 138],
  525 + new EcBlocks(30, new EcBlock(17, 115)),
  526 + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)),
  527 + new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)),
  528 + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16))
  529 + ),
  530 + new self(
  531 + 33,
  532 + [6, 30, 58, 86, 114, 142],
  533 + new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)),
  534 + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)),
  535 + new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)),
  536 + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16))
  537 + ),
  538 + new self(
  539 + 34,
  540 + [6, 34, 62, 90, 118, 146],
  541 + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)),
  542 + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)),
  543 + new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)),
  544 + new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17))
  545 + ),
  546 + new self(
  547 + 35,
  548 + [6, 30, 54, 78, 102, 126, 150],
  549 + new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)),
  550 + new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)),
  551 + new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)),
  552 + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16))
  553 + ),
  554 + new self(
  555 + 36,
  556 + [6, 24, 50, 76, 102, 128, 154],
  557 + new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)),
  558 + new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)),
  559 + new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)),
  560 + new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16))
  561 + ),
  562 + new self(
  563 + 37,
  564 + [6, 28, 54, 80, 106, 132, 158],
  565 + new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)),
  566 + new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)),
  567 + new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)),
  568 + new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16))
  569 + ),
  570 + new self(
  571 + 38,
  572 + [6, 32, 58, 84, 110, 136, 162],
  573 + new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)),
  574 + new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)),
  575 + new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)),
  576 + new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16))
  577 + ),
  578 + new self(
  579 + 39,
  580 + [6, 26, 54, 82, 110, 138, 166],
  581 + new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)),
  582 + new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)),
  583 + new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)),
  584 + new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16))
  585 + ),
  586 + new self(
  587 + 40,
  588 + [6, 30, 58, 86, 114, 142, 170],
  589 + new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)),
  590 + new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)),
  591 + new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)),
  592 + new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16))
  593 + ),
  594 + ];
  595 + }
  596 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Encoder;
  5 +
  6 +use SplFixedArray;
  7 +
  8 +/**
  9 + * Block pair.
  10 + */
  11 +final class BlockPair
  12 +{
  13 + /**
  14 + * Data bytes in the block.
  15 + *
  16 + * @var SplFixedArray<int>
  17 + */
  18 + private $dataBytes;
  19 +
  20 + /**
  21 + * Error correction bytes in the block.
  22 + *
  23 + * @var SplFixedArray<int>
  24 + */
  25 + private $errorCorrectionBytes;
  26 +
  27 + /**
  28 + * Creates a new block pair.
  29 + *
  30 + * @param SplFixedArray<int> $data
  31 + * @param SplFixedArray<int> $errorCorrection
  32 + */
  33 + public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection)
  34 + {
  35 + $this->dataBytes = $data;
  36 + $this->errorCorrectionBytes = $errorCorrection;
  37 + }
  38 +
  39 + /**
  40 + * Gets the data bytes.
  41 + *
  42 + * @return SplFixedArray<int>
  43 + */
  44 + public function getDataBytes() : SplFixedArray
  45 + {
  46 + return $this->dataBytes;
  47 + }
  48 +
  49 + /**
  50 + * Gets the error correction bytes.
  51 + *
  52 + * @return SplFixedArray<int>
  53 + */
  54 + public function getErrorCorrectionBytes() : SplFixedArray
  55 + {
  56 + return $this->errorCorrectionBytes;
  57 + }
  58 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Encoder;
  5 +
  6 +use SplFixedArray;
  7 +use Traversable;
  8 +
  9 +/**
  10 + * Byte matrix.
  11 + */
  12 +final class ByteMatrix
  13 +{
  14 + /**
  15 + * Bytes in the matrix, represented as array.
  16 + *
  17 + * @var SplFixedArray<SplFixedArray<int>>
  18 + */
  19 + private $bytes;
  20 +
  21 + /**
  22 + * Width of the matrix.
  23 + *
  24 + * @var int
  25 + */
  26 + private $width;
  27 +
  28 + /**
  29 + * Height of the matrix.
  30 + *
  31 + * @var int
  32 + */
  33 + private $height;
  34 +
  35 + public function __construct(int $width, int $height)
  36 + {
  37 + $this->height = $height;
  38 + $this->width = $width;
  39 + $this->bytes = new SplFixedArray($height);
  40 +
  41 + for ($y = 0; $y < $height; ++$y) {
  42 + $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0));
  43 + }
  44 + }
  45 +
  46 + /**
  47 + * Gets the width of the matrix.
  48 + */
  49 + public function getWidth() : int
  50 + {
  51 + return $this->width;
  52 + }
  53 +
  54 + /**
  55 + * Gets the height of the matrix.
  56 + */
  57 + public function getHeight() : int
  58 + {
  59 + return $this->height;
  60 + }
  61 +
  62 + /**
  63 + * Gets the internal representation of the matrix.
  64 + *
  65 + * @return SplFixedArray<SplFixedArray<int>>
  66 + */
  67 + public function getArray() : SplFixedArray
  68 + {
  69 + return $this->bytes;
  70 + }
  71 +
  72 + /**
  73 + * @return Traversable<int>
  74 + */
  75 + public function getBytes() : Traversable
  76 + {
  77 + foreach ($this->bytes as $row) {
  78 + foreach ($row as $byte) {
  79 + yield $byte;
  80 + }
  81 + }
  82 + }
  83 +
  84 + /**
  85 + * Gets the byte for a specific position.
  86 + */
  87 + public function get(int $x, int $y) : int
  88 + {
  89 + return $this->bytes[$y][$x];
  90 + }
  91 +
  92 + /**
  93 + * Sets the byte for a specific position.
  94 + */
  95 + public function set(int $x, int $y, int $value) : void
  96 + {
  97 + $this->bytes[$y][$x] = $value;
  98 + }
  99 +
  100 + /**
  101 + * Clears the matrix with a specific value.
  102 + */
  103 + public function clear(int $value) : void
  104 + {
  105 + for ($y = 0; $y < $this->height; ++$y) {
  106 + for ($x = 0; $x < $this->width; ++$x) {
  107 + $this->bytes[$y][$x] = $value;
  108 + }
  109 + }
  110 + }
  111 +
  112 + public function __clone()
  113 + {
  114 + $this->bytes = clone $this->bytes;
  115 +
  116 + foreach ($this->bytes as $index => $row) {
  117 + $this->bytes[$index] = clone $row;
  118 + }
  119 + }
  120 +
  121 + /**
  122 + * Returns a string representation of the matrix.
  123 + */
  124 + public function __toString() : string
  125 + {
  126 + $result = '';
  127 +
  128 + for ($y = 0; $y < $this->height; $y++) {
  129 + for ($x = 0; $x < $this->width; $x++) {
  130 + switch ($this->bytes[$y][$x]) {
  131 + case 0:
  132 + $result .= ' 0';
  133 + break;
  134 +
  135 + case 1:
  136 + $result .= ' 1';
  137 + break;
  138 +
  139 + default:
  140 + $result .= ' ';
  141 + break;
  142 + }
  143 + }
  144 +
  145 + $result .= "\n";
  146 + }
  147 +
  148 + return $result;
  149 + }
  150 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Encoder;
  5 +
  6 +use BaconQrCode\Common\BitArray;
  7 +use BaconQrCode\Common\CharacterSetEci;
  8 +use BaconQrCode\Common\ErrorCorrectionLevel;
  9 +use BaconQrCode\Common\Mode;
  10 +use BaconQrCode\Common\ReedSolomonCodec;
  11 +use BaconQrCode\Common\Version;
  12 +use BaconQrCode\Exception\WriterException;
  13 +use SplFixedArray;
  14 +
  15 +/**
  16 + * Encoder.
  17 + */
  18 +final class Encoder
  19 +{
  20 + /**
  21 + * Default byte encoding.
  22 + */
  23 + public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1';
  24 +
  25 + /**
  26 + * The original table is defined in the table 5 of JISX0510:2004 (p.19).
  27 + */
  28 + private const ALPHANUMERIC_TABLE = [
  29 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
  30 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
  31 + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
  32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
  33 + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
  34 + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
  35 + ];
  36 +
  37 + /**
  38 + * Codec cache.
  39 + *
  40 + * @var array
  41 + */
  42 + private static $codecs = [];
  43 +
  44 + /**
  45 + * Encodes "content" with the error correction level "ecLevel".
  46 + */
  47 + public static function encode(
  48 + string $content,
  49 + ErrorCorrectionLevel $ecLevel,
  50 + string $encoding = self::DEFAULT_BYTE_MODE_ECODING
  51 + ) : QrCode {
  52 + // Pick an encoding mode appropriate for the content. Note that this
  53 + // will not attempt to use multiple modes / segments even if that were
  54 + // more efficient. Would be nice.
  55 + $mode = self::chooseMode($content, $encoding);
  56 +
  57 + // This will store the header information, like mode and length, as well
  58 + // as "header" segments like an ECI segment.
  59 + $headerBits = new BitArray();
  60 +
  61 + // Append ECI segment if applicable
  62 + if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) {
  63 + $eci = CharacterSetEci::getCharacterSetEciByName($encoding);
  64 +
  65 + if (null !== $eci) {
  66 + self::appendEci($eci, $headerBits);
  67 + }
  68 + }
  69 +
  70 + // (With ECI in place,) Write the mode marker
  71 + self::appendModeInfo($mode, $headerBits);
  72 +
  73 + // Collect data within the main segment, separately, to count its size
  74 + // if needed. Don't add it to main payload yet.
  75 + $dataBits = new BitArray();
  76 + self::appendBytes($content, $mode, $dataBits, $encoding);
  77 +
  78 + // Hard part: need to know version to know how many bits length takes.
  79 + // But need to know how many bits it takes to know version. First we
  80 + // take a guess at version by assuming version will be the minimum, 1:
  81 + $provisionalBitsNeeded = $headerBits->getSize()
  82 + + $mode->getCharacterCountBits(Version::getVersionForNumber(1))
  83 + + $dataBits->getSize();
  84 + $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);
  85 +
  86 + // Use that guess to calculate the right version. I am still not sure
  87 + // this works in 100% of cases.
  88 + $bitsNeeded = $headerBits->getSize()
  89 + + $mode->getCharacterCountBits($provisionalVersion)
  90 + + $dataBits->getSize();
  91 + $version = self::chooseVersion($bitsNeeded, $ecLevel);
  92 +
  93 + $headerAndDataBits = new BitArray();
  94 + $headerAndDataBits->appendBitArray($headerBits);
  95 +
  96 + // Find "length" of main segment and write it.
  97 + $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content));
  98 + self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);
  99 +
  100 + // Put data together into the overall payload.
  101 + $headerAndDataBits->appendBitArray($dataBits);
  102 + $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
  103 + $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();
  104 +
  105 + // Terminate the bits properly.
  106 + self::terminateBits($numDataBytes, $headerAndDataBits);
  107 +
  108 + // Interleave data bits with error correction code.
  109 + $finalBits = self::interleaveWithEcBytes(
  110 + $headerAndDataBits,
  111 + $version->getTotalCodewords(),
  112 + $numDataBytes,
  113 + $ecBlocks->getNumBlocks()
  114 + );
  115 +
  116 + // Choose the mask pattern.
  117 + $dimension = $version->getDimensionForVersion();
  118 + $matrix = new ByteMatrix($dimension, $dimension);
  119 + $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);
  120 +
  121 + // Build the matrix.
  122 + MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);
  123 +
  124 + return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix);
  125 + }
  126 +
  127 + /**
  128 + * Gets the alphanumeric code for a byte.
  129 + */
  130 + private static function getAlphanumericCode(int $code) : int
  131 + {
  132 + if (isset(self::ALPHANUMERIC_TABLE[$code])) {
  133 + return self::ALPHANUMERIC_TABLE[$code];
  134 + }
  135 +
  136 + return -1;
  137 + }
  138 +
  139 + /**
  140 + * Chooses the best mode for a given content.
  141 + */
  142 + private static function chooseMode(string $content, string $encoding = null) : Mode
  143 + {
  144 + if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
  145 + return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
  146 + }
  147 +
  148 + $hasNumeric = false;
  149 + $hasAlphanumeric = false;
  150 + $contentLength = strlen($content);
  151 +
  152 + for ($i = 0; $i < $contentLength; ++$i) {
  153 + $char = $content[$i];
  154 +
  155 + if (ctype_digit($char)) {
  156 + $hasNumeric = true;
  157 + } elseif (-1 !== self::getAlphanumericCode(ord($char))) {
  158 + $hasAlphanumeric = true;
  159 + } else {
  160 + return Mode::BYTE();
  161 + }
  162 + }
  163 +
  164 + if ($hasAlphanumeric) {
  165 + return Mode::ALPHANUMERIC();
  166 + } elseif ($hasNumeric) {
  167 + return Mode::NUMERIC();
  168 + }
  169 +
  170 + return Mode::BYTE();
  171 + }
  172 +
  173 + /**
  174 + * Calculates the mask penalty for a matrix.
  175 + */
  176 + private static function calculateMaskPenalty(ByteMatrix $matrix) : int
  177 + {
  178 + return (
  179 + MaskUtil::applyMaskPenaltyRule1($matrix)
  180 + + MaskUtil::applyMaskPenaltyRule2($matrix)
  181 + + MaskUtil::applyMaskPenaltyRule3($matrix)
  182 + + MaskUtil::applyMaskPenaltyRule4($matrix)
  183 + );
  184 + }
  185 +
  186 + /**
  187 + * Checks if content only consists of double-byte kanji characters.
  188 + */
  189 + private static function isOnlyDoubleByteKanji(string $content) : bool
  190 + {
  191 + $bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
  192 +
  193 + if (false === $bytes) {
  194 + return false;
  195 + }
  196 +
  197 + $length = strlen($bytes);
  198 +
  199 + if (0 !== $length % 2) {
  200 + return false;
  201 + }
  202 +
  203 + for ($i = 0; $i < $length; $i += 2) {
  204 + $byte = $bytes[$i] & 0xff;
  205 +
  206 + if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
  207 + return false;
  208 + }
  209 + }
  210 +
  211 + return true;
  212 + }
  213 +
  214 + /**
  215 + * Chooses the best mask pattern for a matrix.
  216 + */
  217 + private static function chooseMaskPattern(
  218 + BitArray $bits,
  219 + ErrorCorrectionLevel $ecLevel,
  220 + Version $version,
  221 + ByteMatrix $matrix
  222 + ) : int {
  223 + $minPenalty = PHP_INT_MAX;
  224 + $bestMaskPattern = -1;
  225 +
  226 + for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) {
  227 + MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
  228 + $penalty = self::calculateMaskPenalty($matrix);
  229 +
  230 + if ($penalty < $minPenalty) {
  231 + $minPenalty = $penalty;
  232 + $bestMaskPattern = $maskPattern;
  233 + }
  234 + }
  235 +
  236 + return $bestMaskPattern;
  237 + }
  238 +
  239 + /**
  240 + * Chooses the best version for the input.
  241 + *
  242 + * @throws WriterException if data is too big
  243 + */
  244 + private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version
  245 + {
  246 + for ($versionNum = 1; $versionNum <= 40; ++$versionNum) {
  247 + $version = Version::getVersionForNumber($versionNum);
  248 + $numBytes = $version->getTotalCodewords();
  249 +
  250 + $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
  251 + $numEcBytes = $ecBlocks->getTotalEcCodewords();
  252 +
  253 + $numDataBytes = $numBytes - $numEcBytes;
  254 + $totalInputBytes = intdiv($numInputBits + 8, 8);
  255 +
  256 + if ($numDataBytes >= $totalInputBytes) {
  257 + return $version;
  258 + }
  259 + }
  260 +
  261 + throw new WriterException('Data too big');
  262 + }
  263 +
  264 + /**
  265 + * Terminates the bits in a bit array.
  266 + *
  267 + * @throws WriterException if data bits cannot fit in the QR code
  268 + * @throws WriterException if bits size does not equal the capacity
  269 + */
  270 + private static function terminateBits(int $numDataBytes, BitArray $bits) : void
  271 + {
  272 + $capacity = $numDataBytes << 3;
  273 +
  274 + if ($bits->getSize() > $capacity) {
  275 + throw new WriterException('Data bits cannot fit in the QR code');
  276 + }
  277 +
  278 + for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) {
  279 + $bits->appendBit(false);
  280 + }
  281 +
  282 + $numBitsInLastByte = $bits->getSize() & 0x7;
  283 +
  284 + if ($numBitsInLastByte > 0) {
  285 + for ($i = $numBitsInLastByte; $i < 8; ++$i) {
  286 + $bits->appendBit(false);
  287 + }
  288 + }
  289 +
  290 + $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();
  291 +
  292 + for ($i = 0; $i < $numPaddingBytes; ++$i) {
  293 + $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8);
  294 + }
  295 +
  296 + if ($bits->getSize() !== $capacity) {
  297 + throw new WriterException('Bits size does not equal capacity');
  298 + }
  299 + }
  300 +
  301 + /**
  302 + * Gets number of data- and EC bytes for a block ID.
  303 + *
  304 + * @return int[]
  305 + * @throws WriterException if block ID is too large
  306 + * @throws WriterException if EC bytes mismatch
  307 + * @throws WriterException if RS blocks mismatch
  308 + * @throws WriterException if total bytes mismatch
  309 + */
  310 + private static function getNumDataBytesAndNumEcBytesForBlockId(
  311 + int $numTotalBytes,
  312 + int $numDataBytes,
  313 + int $numRsBlocks,
  314 + int $blockId
  315 + ) : array {
  316 + if ($blockId >= $numRsBlocks) {
  317 + throw new WriterException('Block ID too large');
  318 + }
  319 +
  320 + $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
  321 + $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
  322 + $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks);
  323 + $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
  324 + $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks);
  325 + $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
  326 + $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
  327 + $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;
  328 +
  329 + if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
  330 + throw new WriterException('EC bytes mismatch');
  331 + }
  332 +
  333 + if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
  334 + throw new WriterException('RS blocks mismatch');
  335 + }
  336 +
  337 + if ($numTotalBytes !==
  338 + (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
  339 + + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
  340 + ) {
  341 + throw new WriterException('Total bytes mismatch');
  342 + }
  343 +
  344 + if ($blockId < $numRsBlocksInGroup1) {
  345 + return [$numDataBytesInGroup1, $numEcBytesInGroup1];
  346 + } else {
  347 + return [$numDataBytesInGroup2, $numEcBytesInGroup2];
  348 + }
  349 + }
  350 +
  351 + /**
  352 + * Interleaves data with EC bytes.
  353 + *
  354 + * @throws WriterException if number of bits and data bytes does not match
  355 + * @throws WriterException if data bytes does not match offset
  356 + * @throws WriterException if an interleaving error occurs
  357 + */
  358 + private static function interleaveWithEcBytes(
  359 + BitArray $bits,
  360 + int $numTotalBytes,
  361 + int $numDataBytes,
  362 + int $numRsBlocks
  363 + ) : BitArray {
  364 + if ($bits->getSizeInBytes() !== $numDataBytes) {
  365 + throw new WriterException('Number of bits and data bytes does not match');
  366 + }
  367 +
  368 + $dataBytesOffset = 0;
  369 + $maxNumDataBytes = 0;
  370 + $maxNumEcBytes = 0;
  371 +
  372 + $blocks = new SplFixedArray($numRsBlocks);
  373 +
  374 + for ($i = 0; $i < $numRsBlocks; ++$i) {
  375 + list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
  376 + $numTotalBytes,
  377 + $numDataBytes,
  378 + $numRsBlocks,
  379 + $i
  380 + );
  381 +
  382 + $size = $numDataBytesInBlock;
  383 + $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
  384 + $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
  385 + $blocks[$i] = new BlockPair($dataBytes, $ecBytes);
  386 +
  387 + $maxNumDataBytes = max($maxNumDataBytes, $size);
  388 + $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
  389 + $dataBytesOffset += $numDataBytesInBlock;
  390 + }
  391 +
  392 + if ($numDataBytes !== $dataBytesOffset) {
  393 + throw new WriterException('Data bytes does not match offset');
  394 + }
  395 +
  396 + $result = new BitArray();
  397 +
  398 + for ($i = 0; $i < $maxNumDataBytes; ++$i) {
  399 + foreach ($blocks as $block) {
  400 + $dataBytes = $block->getDataBytes();
  401 +
  402 + if ($i < count($dataBytes)) {
  403 + $result->appendBits($dataBytes[$i], 8);
  404 + }
  405 + }
  406 + }
  407 +
  408 + for ($i = 0; $i < $maxNumEcBytes; ++$i) {
  409 + foreach ($blocks as $block) {
  410 + $ecBytes = $block->getErrorCorrectionBytes();
  411 +
  412 + if ($i < count($ecBytes)) {
  413 + $result->appendBits($ecBytes[$i], 8);
  414 + }
  415 + }
  416 + }
  417 +
  418 + if ($numTotalBytes !== $result->getSizeInBytes()) {
  419 + throw new WriterException(
  420 + 'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'
  421 + );
  422 + }
  423 +
  424 + return $result;
  425 + }
  426 +
  427 + /**
  428 + * Generates EC bytes for given data.
  429 + *
  430 + * @param SplFixedArray<int> $dataBytes
  431 + * @return SplFixedArray<int>
  432 + */
  433 + private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray
  434 + {
  435 + $numDataBytes = count($dataBytes);
  436 + $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);
  437 +
  438 + for ($i = 0; $i < $numDataBytes; $i++) {
  439 + $toEncode[$i] = $dataBytes[$i] & 0xff;
  440 + }
  441 +
  442 + $ecBytes = new SplFixedArray($numEcBytesInBlock);
  443 + $codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
  444 + $codec->encode($toEncode, $ecBytes);
  445 +
  446 + return $ecBytes;
  447 + }
  448 +
  449 + /**
  450 + * Gets an RS codec and caches it.
  451 + */
  452 + private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec
  453 + {
  454 + $cacheId = $numDataBytes . '-' . $numEcBytesInBlock;
  455 +
  456 + if (isset(self::$codecs[$cacheId])) {
  457 + return self::$codecs[$cacheId];
  458 + }
  459 +
  460 + return self::$codecs[$cacheId] = new ReedSolomonCodec(
  461 + 8,
  462 + 0x11d,
  463 + 0,
  464 + 1,
  465 + $numEcBytesInBlock,
  466 + 255 - $numDataBytes - $numEcBytesInBlock
  467 + );
  468 + }
  469 +
  470 + /**
  471 + * Appends mode information to a bit array.
  472 + */
  473 + private static function appendModeInfo(Mode $mode, BitArray $bits) : void
  474 + {
  475 + $bits->appendBits($mode->getBits(), 4);
  476 + }
  477 +
  478 + /**
  479 + * Appends length information to a bit array.
  480 + *
  481 + * @throws WriterException if num letters is bigger than expected
  482 + */
  483 + private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void
  484 + {
  485 + $numBits = $mode->getCharacterCountBits($version);
  486 +
  487 + if ($numLetters >= (1 << $numBits)) {
  488 + throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
  489 + }
  490 +
  491 + $bits->appendBits($numLetters, $numBits);
  492 + }
  493 +
  494 + /**
  495 + * Appends bytes to a bit array in a specific mode.
  496 + *
  497 + * @throws WriterException if an invalid mode was supplied
  498 + */
  499 + private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void
  500 + {
  501 + switch ($mode) {
  502 + case Mode::NUMERIC():
  503 + self::appendNumericBytes($content, $bits);
  504 + break;
  505 +
  506 + case Mode::ALPHANUMERIC():
  507 + self::appendAlphanumericBytes($content, $bits);
  508 + break;
  509 +
  510 + case Mode::BYTE():
  511 + self::append8BitBytes($content, $bits, $encoding);
  512 + break;
  513 +
  514 + case Mode::KANJI():
  515 + self::appendKanjiBytes($content, $bits);
  516 + break;
  517 +
  518 + default:
  519 + throw new WriterException('Invalid mode: ' . $mode);
  520 + }
  521 + }
  522 +
  523 + /**
  524 + * Appends numeric bytes to a bit array.
  525 + */
  526 + private static function appendNumericBytes(string $content, BitArray $bits) : void
  527 + {
  528 + $length = strlen($content);
  529 + $i = 0;
  530 +
  531 + while ($i < $length) {
  532 + $num1 = (int) $content[$i];
  533 +
  534 + if ($i + 2 < $length) {
  535 + // Encode three numeric letters in ten bits.
  536 + $num2 = (int) $content[$i + 1];
  537 + $num3 = (int) $content[$i + 2];
  538 + $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
  539 + $i += 3;
  540 + } elseif ($i + 1 < $length) {
  541 + // Encode two numeric letters in seven bits.
  542 + $num2 = (int) $content[$i + 1];
  543 + $bits->appendBits($num1 * 10 + $num2, 7);
  544 + $i += 2;
  545 + } else {
  546 + // Encode one numeric letter in four bits.
  547 + $bits->appendBits($num1, 4);
  548 + ++$i;
  549 + }
  550 + }
  551 + }
  552 +
  553 + /**
  554 + * Appends alpha-numeric bytes to a bit array.
  555 + *
  556 + * @throws WriterException if an invalid alphanumeric code was found
  557 + */
  558 + private static function appendAlphanumericBytes(string $content, BitArray $bits) : void
  559 + {
  560 + $length = strlen($content);
  561 + $i = 0;
  562 +
  563 + while ($i < $length) {
  564 + $code1 = self::getAlphanumericCode(ord($content[$i]));
  565 +
  566 + if (-1 === $code1) {
  567 + throw new WriterException('Invalid alphanumeric code');
  568 + }
  569 +
  570 + if ($i + 1 < $length) {
  571 + $code2 = self::getAlphanumericCode(ord($content[$i + 1]));
  572 +
  573 + if (-1 === $code2) {
  574 + throw new WriterException('Invalid alphanumeric code');
  575 + }
  576 +
  577 + // Encode two alphanumeric letters in 11 bits.
  578 + $bits->appendBits($code1 * 45 + $code2, 11);
  579 + $i += 2;
  580 + } else {
  581 + // Encode one alphanumeric letter in six bits.
  582 + $bits->appendBits($code1, 6);
  583 + ++$i;
  584 + }
  585 + }
  586 + }
  587 +
  588 + /**
  589 + * Appends regular 8-bit bytes to a bit array.
  590 + *
  591 + * @throws WriterException if content cannot be encoded to target encoding
  592 + */
  593 + private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void
  594 + {
  595 + $bytes = @iconv('utf-8', $encoding, $content);
  596 +
  597 + if (false === $bytes) {
  598 + throw new WriterException('Could not encode content to ' . $encoding);
  599 + }
  600 +
  601 + $length = strlen($bytes);
  602 +
  603 + for ($i = 0; $i < $length; $i++) {
  604 + $bits->appendBits(ord($bytes[$i]), 8);
  605 + }
  606 + }
  607 +
  608 + /**
  609 + * Appends KANJI bytes to a bit array.
  610 + *
  611 + * @throws WriterException if content does not seem to be encoded in SHIFT-JIS
  612 + * @throws WriterException if an invalid byte sequence occurs
  613 + */
  614 + private static function appendKanjiBytes(string $content, BitArray $bits) : void
  615 + {
  616 + if (strlen($content) % 2 > 0) {
  617 + // We just do a simple length check here. The for loop will check
  618 + // individual characters.
  619 + throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
  620 + }
  621 +
  622 + $length = strlen($content);
  623 +
  624 + for ($i = 0; $i < $length; $i += 2) {
  625 + $byte1 = ord($content[$i]) & 0xff;
  626 + $byte2 = ord($content[$i + 1]) & 0xff;
  627 + $code = ($byte1 << 8) | $byte2;
  628 +
  629 + if ($code >= 0x8140 && $code <= 0x9ffc) {
  630 + $subtracted = $code - 0x8140;
  631 + } elseif ($code >= 0xe040 && $code <= 0xebbf) {
  632 + $subtracted = $code - 0xc140;
  633 + } else {
  634 + throw new WriterException('Invalid byte sequence');
  635 + }
  636 +
  637 + $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);
  638 +
  639 + $bits->appendBits($encoded, 13);
  640 + }
  641 + }
  642 +
  643 + /**
  644 + * Appends ECI information to a bit array.
  645 + */
  646 + private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void
  647 + {
  648 + $mode = Mode::ECI();
  649 + $bits->appendBits($mode->getBits(), 4);
  650 + $bits->appendBits($eci->getValue(), 8);
  651 + }
  652 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Encoder;
  5 +
  6 +use BaconQrCode\Common\BitUtils;
  7 +use BaconQrCode\Exception\InvalidArgumentException;
  8 +
  9 +/**
  10 + * Mask utility.
  11 + */
  12 +final class MaskUtil
  13 +{
  14 + /**#@+
  15 + * Penalty weights from section 6.8.2.1
  16 + */
  17 + const N1 = 3;
  18 + const N2 = 3;
  19 + const N3 = 40;
  20 + const N4 = 10;
  21 + /**#@-*/
  22 +
  23 + private function __construct()
  24 + {
  25 + }
  26 +
  27 + /**
  28 + * Applies mask penalty rule 1 and returns the penalty.
  29 + *
  30 + * Finds repetitive cells with the same color and gives penalty to them.
  31 + * Example: 00000 or 11111.
  32 + */
  33 + public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int
  34 + {
  35 + return (
  36 + self::applyMaskPenaltyRule1Internal($matrix, true)
  37 + + self::applyMaskPenaltyRule1Internal($matrix, false)
  38 + );
  39 + }
  40 +
  41 + /**
  42 + * Applies mask penalty rule 2 and returns the penalty.
  43 + *
  44 + * Finds 2x2 blocks with the same color and gives penalty to them. This is
  45 + * actually equivalent to the spec's rule, which is to find MxN blocks and
  46 + * give a penalty proportional to (M-1)x(N-1), because this is the number of
  47 + * 2x2 blocks inside such a block.
  48 + */
  49 + public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int
  50 + {
  51 + $penalty = 0;
  52 + $array = $matrix->getArray();
  53 + $width = $matrix->getWidth();
  54 + $height = $matrix->getHeight();
  55 +
  56 + for ($y = 0; $y < $height - 1; ++$y) {
  57 + for ($x = 0; $x < $width - 1; ++$x) {
  58 + $value = $array[$y][$x];
  59 +
  60 + if ($value === $array[$y][$x + 1]
  61 + && $value === $array[$y + 1][$x]
  62 + && $value === $array[$y + 1][$x + 1]
  63 + ) {
  64 + ++$penalty;
  65 + }
  66 + }
  67 + }
  68 +
  69 + return self::N2 * $penalty;
  70 + }
  71 +
  72 + /**
  73 + * Applies mask penalty rule 3 and returns the penalty.
  74 + *
  75 + * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
  76 + * to them. If we find patterns like 000010111010000, we give penalties
  77 + * twice (i.e. 40 * 2).
  78 + */
  79 + public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int
  80 + {
  81 + $penalty = 0;
  82 + $array = $matrix->getArray();
  83 + $width = $matrix->getWidth();
  84 + $height = $matrix->getHeight();
  85 +
  86 + for ($y = 0; $y < $height; ++$y) {
  87 + for ($x = 0; $x < $width; ++$x) {
  88 + if ($x + 6 < $width
  89 + && 1 === $array[$y][$x]
  90 + && 0 === $array[$y][$x + 1]
  91 + && 1 === $array[$y][$x + 2]
  92 + && 1 === $array[$y][$x + 3]
  93 + && 1 === $array[$y][$x + 4]
  94 + && 0 === $array[$y][$x + 5]
  95 + && 1 === $array[$y][$x + 6]
  96 + && (
  97 + (
  98 + $x + 10 < $width
  99 + && 0 === $array[$y][$x + 7]
  100 + && 0 === $array[$y][$x + 8]
  101 + && 0 === $array[$y][$x + 9]
  102 + && 0 === $array[$y][$x + 10]
  103 + )
  104 + || (
  105 + $x - 4 >= 0
  106 + && 0 === $array[$y][$x - 1]
  107 + && 0 === $array[$y][$x - 2]
  108 + && 0 === $array[$y][$x - 3]
  109 + && 0 === $array[$y][$x - 4]
  110 + )
  111 + )
  112 + ) {
  113 + $penalty += self::N3;
  114 + }
  115 +
  116 + if ($y + 6 < $height
  117 + && 1 === $array[$y][$x]
  118 + && 0 === $array[$y + 1][$x]
  119 + && 1 === $array[$y + 2][$x]
  120 + && 1 === $array[$y + 3][$x]
  121 + && 1 === $array[$y + 4][$x]
  122 + && 0 === $array[$y + 5][$x]
  123 + && 1 === $array[$y + 6][$x]
  124 + && (
  125 + (
  126 + $y + 10 < $height
  127 + && 0 === $array[$y + 7][$x]
  128 + && 0 === $array[$y + 8][$x]
  129 + && 0 === $array[$y + 9][$x]
  130 + && 0 === $array[$y + 10][$x]
  131 + )
  132 + || (
  133 + $y - 4 >= 0
  134 + && 0 === $array[$y - 1][$x]
  135 + && 0 === $array[$y - 2][$x]
  136 + && 0 === $array[$y - 3][$x]
  137 + && 0 === $array[$y - 4][$x]
  138 + )
  139 + )
  140 + ) {
  141 + $penalty += self::N3;
  142 + }
  143 + }
  144 + }
  145 +
  146 + return $penalty;
  147 + }
  148 +
  149 + /**
  150 + * Applies mask penalty rule 4 and returns the penalty.
  151 + *
  152 + * Calculates the ratio of dark cells and gives penalty if the ratio is far
  153 + * from 50%. It gives 10 penalty for 5% distance.
  154 + */
  155 + public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int
  156 + {
  157 + $numDarkCells = 0;
  158 +
  159 + $array = $matrix->getArray();
  160 + $width = $matrix->getWidth();
  161 + $height = $matrix->getHeight();
  162 +
  163 + for ($y = 0; $y < $height; ++$y) {
  164 + $arrayY = $array[$y];
  165 +
  166 + for ($x = 0; $x < $width; ++$x) {
  167 + if (1 === $arrayY[$x]) {
  168 + ++$numDarkCells;
  169 + }
  170 + }
  171 + }
  172 +
  173 + $numTotalCells = $height * $width;
  174 + $darkRatio = $numDarkCells / $numTotalCells;
  175 + $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20);
  176 +
  177 + return $fixedPercentVariances * self::N4;
  178 + }
  179 +
  180 + /**
  181 + * Returns the mask bit for "getMaskPattern" at "x" and "y".
  182 + *
  183 + * See 8.8 of JISX0510:2004 for mask pattern conditions.
  184 + *
  185 + * @throws InvalidArgumentException if an invalid mask pattern was supplied
  186 + */
  187 + public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool
  188 + {
  189 + switch ($maskPattern) {
  190 + case 0:
  191 + $intermediate = ($y + $x) & 0x1;
  192 + break;
  193 +
  194 + case 1:
  195 + $intermediate = $y & 0x1;
  196 + break;
  197 +
  198 + case 2:
  199 + $intermediate = $x % 3;
  200 + break;
  201 +
  202 + case 3:
  203 + $intermediate = ($y + $x) % 3;
  204 + break;
  205 +
  206 + case 4:
  207 + $intermediate = (BitUtils::unsignedRightShift($y, 1) + ($x / 3)) & 0x1;
  208 + break;
  209 +
  210 + case 5:
  211 + $temp = $y * $x;
  212 + $intermediate = ($temp & 0x1) + ($temp % 3);
  213 + break;
  214 +
  215 + case 6:
  216 + $temp = $y * $x;
  217 + $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1;
  218 + break;
  219 +
  220 + case 7:
  221 + $temp = $y * $x;
  222 + $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1;
  223 + break;
  224 +
  225 + default:
  226 + throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern);
  227 + }
  228 +
  229 + return 0 == $intermediate;
  230 + }
  231 +
  232 + /**
  233 + * Helper function for applyMaskPenaltyRule1.
  234 + *
  235 + * We need this for doing this calculation in both vertical and horizontal
  236 + * orders respectively.
  237 + */
  238 + private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int
  239 + {
  240 + $penalty = 0;
  241 + $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth();
  242 + $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight();
  243 + $array = $matrix->getArray();
  244 +
  245 + for ($i = 0; $i < $iLimit; ++$i) {
  246 + $numSameBitCells = 0;
  247 + $prevBit = -1;
  248 +
  249 + for ($j = 0; $j < $jLimit; $j++) {
  250 + $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i];
  251 +
  252 + if ($bit === $prevBit) {
  253 + ++$numSameBitCells;
  254 + } else {
  255 + if ($numSameBitCells >= 5) {
  256 + $penalty += self::N1 + ($numSameBitCells - 5);
  257 + }
  258 +
  259 + $numSameBitCells = 1;
  260 + $prevBit = $bit;
  261 + }
  262 + }
  263 +
  264 + if ($numSameBitCells >= 5) {
  265 + $penalty += self::N1 + ($numSameBitCells - 5);
  266 + }
  267 + }
  268 +
  269 + return $penalty;
  270 + }
  271 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Encoder;
  5 +
  6 +use BaconQrCode\Common\BitArray;
  7 +use BaconQrCode\Common\ErrorCorrectionLevel;
  8 +use BaconQrCode\Common\Version;
  9 +use BaconQrCode\Exception\RuntimeException;
  10 +use BaconQrCode\Exception\WriterException;
  11 +
  12 +/**
  13 + * Matrix utility.
  14 + */
  15 +final class MatrixUtil
  16 +{
  17 + /**
  18 + * Position detection pattern.
  19 + */
  20 + private const POSITION_DETECTION_PATTERN = [
  21 + [1, 1, 1, 1, 1, 1, 1],
  22 + [1, 0, 0, 0, 0, 0, 1],
  23 + [1, 0, 1, 1, 1, 0, 1],
  24 + [1, 0, 1, 1, 1, 0, 1],
  25 + [1, 0, 1, 1, 1, 0, 1],
  26 + [1, 0, 0, 0, 0, 0, 1],
  27 + [1, 1, 1, 1, 1, 1, 1],
  28 + ];
  29 +
  30 + /**
  31 + * Position adjustment pattern.
  32 + */
  33 + private const POSITION_ADJUSTMENT_PATTERN = [
  34 + [1, 1, 1, 1, 1],
  35 + [1, 0, 0, 0, 1],
  36 + [1, 0, 1, 0, 1],
  37 + [1, 0, 0, 0, 1],
  38 + [1, 1, 1, 1, 1],
  39 + ];
  40 +
  41 + /**
  42 + * Coordinates for position adjustment patterns for each version.
  43 + */
  44 + private const POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [
  45 + [null, null, null, null, null, null, null], // Version 1
  46 + [ 6, 18, null, null, null, null, null], // Version 2
  47 + [ 6, 22, null, null, null, null, null], // Version 3
  48 + [ 6, 26, null, null, null, null, null], // Version 4
  49 + [ 6, 30, null, null, null, null, null], // Version 5
  50 + [ 6, 34, null, null, null, null, null], // Version 6
  51 + [ 6, 22, 38, null, null, null, null], // Version 7
  52 + [ 6, 24, 42, null, null, null, null], // Version 8
  53 + [ 6, 26, 46, null, null, null, null], // Version 9
  54 + [ 6, 28, 50, null, null, null, null], // Version 10
  55 + [ 6, 30, 54, null, null, null, null], // Version 11
  56 + [ 6, 32, 58, null, null, null, null], // Version 12
  57 + [ 6, 34, 62, null, null, null, null], // Version 13
  58 + [ 6, 26, 46, 66, null, null, null], // Version 14
  59 + [ 6, 26, 48, 70, null, null, null], // Version 15
  60 + [ 6, 26, 50, 74, null, null, null], // Version 16
  61 + [ 6, 30, 54, 78, null, null, null], // Version 17
  62 + [ 6, 30, 56, 82, null, null, null], // Version 18
  63 + [ 6, 30, 58, 86, null, null, null], // Version 19
  64 + [ 6, 34, 62, 90, null, null, null], // Version 20
  65 + [ 6, 28, 50, 72, 94, null, null], // Version 21
  66 + [ 6, 26, 50, 74, 98, null, null], // Version 22
  67 + [ 6, 30, 54, 78, 102, null, null], // Version 23
  68 + [ 6, 28, 54, 80, 106, null, null], // Version 24
  69 + [ 6, 32, 58, 84, 110, null, null], // Version 25
  70 + [ 6, 30, 58, 86, 114, null, null], // Version 26
  71 + [ 6, 34, 62, 90, 118, null, null], // Version 27
  72 + [ 6, 26, 50, 74, 98, 122, null], // Version 28
  73 + [ 6, 30, 54, 78, 102, 126, null], // Version 29
  74 + [ 6, 26, 52, 78, 104, 130, null], // Version 30
  75 + [ 6, 30, 56, 82, 108, 134, null], // Version 31
  76 + [ 6, 34, 60, 86, 112, 138, null], // Version 32
  77 + [ 6, 30, 58, 86, 114, 142, null], // Version 33
  78 + [ 6, 34, 62, 90, 118, 146, null], // Version 34
  79 + [ 6, 30, 54, 78, 102, 126, 150], // Version 35
  80 + [ 6, 24, 50, 76, 102, 128, 154], // Version 36
  81 + [ 6, 28, 54, 80, 106, 132, 158], // Version 37
  82 + [ 6, 32, 58, 84, 110, 136, 162], // Version 38
  83 + [ 6, 26, 54, 82, 110, 138, 166], // Version 39
  84 + [ 6, 30, 58, 86, 114, 142, 170], // Version 40
  85 + ];
  86 +
  87 + /**
  88 + * Type information coordinates.
  89 + */
  90 + private const TYPE_INFO_COORDINATES = [
  91 + [8, 0],
  92 + [8, 1],
  93 + [8, 2],
  94 + [8, 3],
  95 + [8, 4],
  96 + [8, 5],
  97 + [8, 7],
  98 + [8, 8],
  99 + [7, 8],
  100 + [5, 8],
  101 + [4, 8],
  102 + [3, 8],
  103 + [2, 8],
  104 + [1, 8],
  105 + [0, 8],
  106 + ];
  107 +
  108 + /**
  109 + * Version information polynomial.
  110 + */
  111 + private const VERSION_INFO_POLY = 0x1f25;
  112 +
  113 + /**
  114 + * Type information polynomial.
  115 + */
  116 + private const TYPE_INFO_POLY = 0x537;
  117 +
  118 + /**
  119 + * Type information mask pattern.
  120 + */
  121 + private const TYPE_INFO_MASK_PATTERN = 0x5412;
  122 +
  123 + /**
  124 + * Clears a given matrix.
  125 + */
  126 + public static function clearMatrix(ByteMatrix $matrix) : void
  127 + {
  128 + $matrix->clear(-1);
  129 + }
  130 +
  131 + /**
  132 + * Builds a complete matrix.
  133 + */
  134 + public static function buildMatrix(
  135 + BitArray $dataBits,
  136 + ErrorCorrectionLevel $level,
  137 + Version $version,
  138 + int $maskPattern,
  139 + ByteMatrix $matrix
  140 + ) : void {
  141 + self::clearMatrix($matrix);
  142 + self::embedBasicPatterns($version, $matrix);
  143 + self::embedTypeInfo($level, $maskPattern, $matrix);
  144 + self::maybeEmbedVersionInfo($version, $matrix);
  145 + self::embedDataBits($dataBits, $maskPattern, $matrix);
  146 + }
  147 +
  148 + /**
  149 + * Removes the position detection patterns from a matrix.
  150 + *
  151 + * This can be useful if you need to render those patterns separately.
  152 + */
  153 + public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void
  154 + {
  155 + $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
  156 +
  157 + self::removePositionDetectionPattern(0, 0, $matrix);
  158 + self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
  159 + self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
  160 + }
  161 +
  162 + /**
  163 + * Embeds type information into a matrix.
  164 + */
  165 + private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void
  166 + {
  167 + $typeInfoBits = new BitArray();
  168 + self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits);
  169 +
  170 + $typeInfoBitsSize = $typeInfoBits->getSize();
  171 +
  172 + for ($i = 0; $i < $typeInfoBitsSize; ++$i) {
  173 + $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i);
  174 +
  175 + $x1 = self::TYPE_INFO_COORDINATES[$i][0];
  176 + $y1 = self::TYPE_INFO_COORDINATES[$i][1];
  177 +
  178 + $matrix->set($x1, $y1, (int) $bit);
  179 +
  180 + if ($i < 8) {
  181 + $x2 = $matrix->getWidth() - $i - 1;
  182 + $y2 = 8;
  183 + } else {
  184 + $x2 = 8;
  185 + $y2 = $matrix->getHeight() - 7 + ($i - 8);
  186 + }
  187 +
  188 + $matrix->set($x2, $y2, (int) $bit);
  189 + }
  190 + }
  191 +
  192 + /**
  193 + * Generates type information bits and appends them to a bit array.
  194 + *
  195 + * @throws RuntimeException if bit array resulted in invalid size
  196 + */
  197 + private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void
  198 + {
  199 + $typeInfo = ($level->getBits() << 3) | $maskPattern;
  200 + $bits->appendBits($typeInfo, 5);
  201 +
  202 + $bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY);
  203 + $bits->appendBits($bchCode, 10);
  204 +
  205 + $maskBits = new BitArray();
  206 + $maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15);
  207 + $bits->xorBits($maskBits);
  208 +
  209 + if (15 !== $bits->getSize()) {
  210 + throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
  211 + }
  212 + }
  213 +
  214 + /**
  215 + * Embeds version information if required.
  216 + */
  217 + private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void
  218 + {
  219 + if ($version->getVersionNumber() < 7) {
  220 + return;
  221 + }
  222 +
  223 + $versionInfoBits = new BitArray();
  224 + self::makeVersionInfoBits($version, $versionInfoBits);
  225 +
  226 + $bitIndex = 6 * 3 - 1;
  227 +
  228 + for ($i = 0; $i < 6; ++$i) {
  229 + for ($j = 0; $j < 3; ++$j) {
  230 + $bit = $versionInfoBits->get($bitIndex);
  231 + --$bitIndex;
  232 +
  233 + $matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit);
  234 + $matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit);
  235 + }
  236 + }
  237 + }
  238 +
  239 + /**
  240 + * Generates version information bits and appends them to a bit array.
  241 + *
  242 + * @throws RuntimeException if bit array resulted in invalid size
  243 + */
  244 + private static function makeVersionInfoBits(Version $version, BitArray $bits) : void
  245 + {
  246 + $bits->appendBits($version->getVersionNumber(), 6);
  247 +
  248 + $bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY);
  249 + $bits->appendBits($bchCode, 12);
  250 +
  251 + if (18 !== $bits->getSize()) {
  252 + throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
  253 + }
  254 + }
  255 +
  256 + /**
  257 + * Calculates the BCH code for a value and a polynomial.
  258 + */
  259 + private static function calculateBchCode(int $value, int $poly) : int
  260 + {
  261 + $msbSetInPoly = self::findMsbSet($poly);
  262 + $value <<= $msbSetInPoly - 1;
  263 +
  264 + while (self::findMsbSet($value) >= $msbSetInPoly) {
  265 + $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly);
  266 + }
  267 +
  268 + return $value;
  269 + }
  270 +
  271 + /**
  272 + * Finds and MSB set.
  273 + */
  274 + private static function findMsbSet(int $value) : int
  275 + {
  276 + $numDigits = 0;
  277 +
  278 + while (0 !== $value) {
  279 + $value >>= 1;
  280 + ++$numDigits;
  281 + }
  282 +
  283 + return $numDigits;
  284 + }
  285 +
  286 + /**
  287 + * Embeds basic patterns into a matrix.
  288 + */
  289 + private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void
  290 + {
  291 + self::embedPositionDetectionPatternsAndSeparators($matrix);
  292 + self::embedDarkDotAtLeftBottomCorner($matrix);
  293 + self::maybeEmbedPositionAdjustmentPatterns($version, $matrix);
  294 + self::embedTimingPatterns($matrix);
  295 + }
  296 +
  297 + /**
  298 + * Embeds position detection patterns and separators into a byte matrix.
  299 + */
  300 + private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void
  301 + {
  302 + $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
  303 +
  304 + self::embedPositionDetectionPattern(0, 0, $matrix);
  305 + self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
  306 + self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
  307 +
  308 + $hspWidth = 8;
  309 +
  310 + self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix);
  311 + self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix);
  312 + self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix);
  313 +
  314 + $vspSize = 7;
  315 +
  316 + self::embedVerticalSeparationPattern($vspSize, 0, $matrix);
  317 + self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix);
  318 + self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix);
  319 + }
  320 +
  321 + /**
  322 + * Embeds a single position detection pattern into a byte matrix.
  323 + */
  324 + private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
  325 + {
  326 + for ($y = 0; $y < 7; ++$y) {
  327 + for ($x = 0; $x < 7; ++$x) {
  328 + $matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]);
  329 + }
  330 + }
  331 + }
  332 +
  333 + private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
  334 + {
  335 + for ($y = 0; $y < 7; ++$y) {
  336 + for ($x = 0; $x < 7; ++$x) {
  337 + $matrix->set($xStart + $x, $yStart + $y, 0);
  338 + }
  339 + }
  340 + }
  341 +
  342 + /**
  343 + * Embeds a single horizontal separation pattern.
  344 + *
  345 + * @throws RuntimeException if a byte was already set
  346 + */
  347 + private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
  348 + {
  349 + for ($x = 0; $x < 8; $x++) {
  350 + if (-1 !== $matrix->get($xStart + $x, $yStart)) {
  351 + throw new RuntimeException('Byte already set');
  352 + }
  353 +
  354 + $matrix->set($xStart + $x, $yStart, 0);
  355 + }
  356 + }
  357 +
  358 + /**
  359 + * Embeds a single vertical separation pattern.
  360 + *
  361 + * @throws RuntimeException if a byte was already set
  362 + */
  363 + private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
  364 + {
  365 + for ($y = 0; $y < 7; $y++) {
  366 + if (-1 !== $matrix->get($xStart, $yStart + $y)) {
  367 + throw new RuntimeException('Byte already set');
  368 + }
  369 +
  370 + $matrix->set($xStart, $yStart + $y, 0);
  371 + }
  372 + }
  373 +
  374 + /**
  375 + * Embeds a dot at the left bottom corner.
  376 + *
  377 + * @throws RuntimeException if a byte was already set to 0
  378 + */
  379 + private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void
  380 + {
  381 + if (0 === $matrix->get(8, $matrix->getHeight() - 8)) {
  382 + throw new RuntimeException('Byte already set to 0');
  383 + }
  384 +
  385 + $matrix->set(8, $matrix->getHeight() - 8, 1);
  386 + }
  387 +
  388 + /**
  389 + * Embeds position adjustment patterns if required.
  390 + */
  391 + private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void
  392 + {
  393 + if ($version->getVersionNumber() < 2) {
  394 + return;
  395 + }
  396 +
  397 + $index = $version->getVersionNumber() - 1;
  398 +
  399 + $coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index];
  400 + $numCoordinates = count($coordinates);
  401 +
  402 + for ($i = 0; $i < $numCoordinates; ++$i) {
  403 + for ($j = 0; $j < $numCoordinates; ++$j) {
  404 + $y = $coordinates[$i];
  405 + $x = $coordinates[$j];
  406 +
  407 + if (null === $x || null === $y) {
  408 + continue;
  409 + }
  410 +
  411 + if (-1 === $matrix->get($x, $y)) {
  412 + self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix);
  413 + }
  414 + }
  415 + }
  416 + }
  417 +
  418 + /**
  419 + * Embeds a single position adjustment pattern.
  420 + */
  421 + private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
  422 + {
  423 + for ($y = 0; $y < 5; $y++) {
  424 + for ($x = 0; $x < 5; $x++) {
  425 + $matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]);
  426 + }
  427 + }
  428 + }
  429 +
  430 + /**
  431 + * Embeds timing patterns into a matrix.
  432 + */
  433 + private static function embedTimingPatterns(ByteMatrix $matrix) : void
  434 + {
  435 + $matrixWidth = $matrix->getWidth();
  436 +
  437 + for ($i = 8; $i < $matrixWidth - 8; ++$i) {
  438 + $bit = ($i + 1) % 2;
  439 +
  440 + if (-1 === $matrix->get($i, 6)) {
  441 + $matrix->set($i, 6, $bit);
  442 + }
  443 +
  444 + if (-1 === $matrix->get(6, $i)) {
  445 + $matrix->set(6, $i, $bit);
  446 + }
  447 + }
  448 + }
  449 +
  450 + /**
  451 + * Embeds "dataBits" using "getMaskPattern".
  452 + *
  453 + * For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for
  454 + * how to embed data bits.
  455 + *
  456 + * @throws WriterException if not all bits could be consumed
  457 + */
  458 + private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void
  459 + {
  460 + $bitIndex = 0;
  461 + $direction = -1;
  462 +
  463 + // Start from the right bottom cell.
  464 + $x = $matrix->getWidth() - 1;
  465 + $y = $matrix->getHeight() - 1;
  466 +
  467 + while ($x > 0) {
  468 + // Skip vertical timing pattern.
  469 + if (6 === $x) {
  470 + --$x;
  471 + }
  472 +
  473 + while ($y >= 0 && $y < $matrix->getHeight()) {
  474 + for ($i = 0; $i < 2; $i++) {
  475 + $xx = $x - $i;
  476 +
  477 + // Skip the cell if it's not empty.
  478 + if (-1 !== $matrix->get($xx, $y)) {
  479 + continue;
  480 + }
  481 +
  482 + if ($bitIndex < $dataBits->getSize()) {
  483 + $bit = $dataBits->get($bitIndex);
  484 + ++$bitIndex;
  485 + } else {
  486 + // Padding bit. If there is no bit left, we'll fill the
  487 + // left cells with 0, as described in 8.4.9 of
  488 + // JISX0510:2004 (p. 24).
  489 + $bit = false;
  490 + }
  491 +
  492 + // Skip masking if maskPattern is -1.
  493 + if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) {
  494 + $bit = ! $bit;
  495 + }
  496 +
  497 + $matrix->set($xx, $y, (int) $bit);
  498 + }
  499 +
  500 + $y += $direction;
  501 + }
  502 +
  503 + $direction = -$direction;
  504 + $y += $direction;
  505 + $x -= 2;
  506 + }
  507 +
  508 + // All bits should be consumed
  509 + if ($dataBits->getSize() !== $bitIndex) {
  510 + throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')');
  511 + }
  512 + }
  513 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Encoder;
  5 +
  6 +use BaconQrCode\Common\ErrorCorrectionLevel;
  7 +use BaconQrCode\Common\Mode;
  8 +use BaconQrCode\Common\Version;
  9 +
  10 +/**
  11 + * QR code.
  12 + */
  13 +final class QrCode
  14 +{
  15 + /**
  16 + * Number of possible mask patterns.
  17 + */
  18 + public const NUM_MASK_PATTERNS = 8;
  19 +
  20 + /**
  21 + * Mode of the QR code.
  22 + *
  23 + * @var Mode
  24 + */
  25 + private $mode;
  26 +
  27 + /**
  28 + * EC level of the QR code.
  29 + *
  30 + * @var ErrorCorrectionLevel
  31 + */
  32 + private $errorCorrectionLevel;
  33 +
  34 + /**
  35 + * Version of the QR code.
  36 + *
  37 + * @var Version
  38 + */
  39 + private $version;
  40 +
  41 + /**
  42 + * Mask pattern of the QR code.
  43 + *
  44 + * @var int
  45 + */
  46 + private $maskPattern = -1;
  47 +
  48 + /**
  49 + * Matrix of the QR code.
  50 + *
  51 + * @var ByteMatrix
  52 + */
  53 + private $matrix;
  54 +
  55 + public function __construct(
  56 + Mode $mode,
  57 + ErrorCorrectionLevel $errorCorrectionLevel,
  58 + Version $version,
  59 + int $maskPattern,
  60 + ByteMatrix $matrix
  61 + ) {
  62 + $this->mode = $mode;
  63 + $this->errorCorrectionLevel = $errorCorrectionLevel;
  64 + $this->version = $version;
  65 + $this->maskPattern = $maskPattern;
  66 + $this->matrix = $matrix;
  67 + }
  68 +
  69 + /**
  70 + * Gets the mode.
  71 + */
  72 + public function getMode() : Mode
  73 + {
  74 + return $this->mode;
  75 + }
  76 +
  77 + /**
  78 + * Gets the EC level.
  79 + */
  80 + public function getErrorCorrectionLevel() : ErrorCorrectionLevel
  81 + {
  82 + return $this->errorCorrectionLevel;
  83 + }
  84 +
  85 + /**
  86 + * Gets the version.
  87 + */
  88 + public function getVersion() : Version
  89 + {
  90 + return $this->version;
  91 + }
  92 +
  93 + /**
  94 + * Gets the mask pattern.
  95 + */
  96 + public function getMaskPattern() : int
  97 + {
  98 + return $this->maskPattern;
  99 + }
  100 +
  101 + /**
  102 + * Gets the matrix.
  103 + *
  104 + * @return ByteMatrix
  105 + */
  106 + public function getMatrix()
  107 + {
  108 + return $this->matrix;
  109 + }
  110 +
  111 + /**
  112 + * Validates whether a mask pattern is valid.
  113 + */
  114 + public static function isValidMaskPattern(int $maskPattern) : bool
  115 + {
  116 + return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS;
  117 + }
  118 +
  119 + /**
  120 + * Returns a string representation of the QR code.
  121 + */
  122 + public function __toString() : string
  123 + {
  124 + $result = "<<\n"
  125 + . ' mode: ' . $this->mode . "\n"
  126 + . ' ecLevel: ' . $this->errorCorrectionLevel . "\n"
  127 + . ' version: ' . $this->version . "\n"
  128 + . ' maskPattern: ' . $this->maskPattern . "\n";
  129 +
  130 + if ($this->matrix === null) {
  131 + $result .= " matrix: null\n";
  132 + } else {
  133 + $result .= " matrix:\n";
  134 + $result .= $this->matrix;
  135 + }
  136 +
  137 + $result .= ">>\n";
  138 +
  139 + return $result;
  140 + }
  141 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Exception;
  5 +
  6 +use Throwable;
  7 +
  8 +interface ExceptionInterface extends Throwable
  9 +{
  10 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Exception;
  5 +
  6 +final class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
  7 +{
  8 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Exception;
  5 +
  6 +final class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
  7 +{
  8 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Exception;
  5 +
  6 +final class RuntimeException extends \RuntimeException implements ExceptionInterface
  7 +{
  8 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Exception;
  5 +
  6 +final class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
  7 +{
  8 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Exception;
  5 +
  6 +final class WriterException extends \RuntimeException implements ExceptionInterface
  7 +{
  8 +}
  1 +Copyright (c) 2017, Ben Scholzen 'DASPRiD'
  2 +All rights reserved.
  3 +
  4 +Redistribution and use in source and binary forms, with or without
  5 +modification, are permitted provided that the following conditions are met:
  6 +
  7 +1. Redistributions of source code must retain the above copyright notice, this
  8 + list of conditions and the following disclaimer.
  9 +2. Redistributions in binary form must reproduce the above copyright notice,
  10 + this list of conditions and the following disclaimer in the documentation
  11 + and/or other materials provided with the distribution.
  12 +
  13 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  14 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  17 +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  18 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  19 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  20 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  21 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  22 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Renderer\Color;
  5 +
  6 +use BaconQrCode\Exception;
  7 +
  8 +final class Alpha implements ColorInterface
  9 +{
  10 + /**
  11 + * @var int
  12 + */
  13 + private $alpha;
  14 +
  15 + /**
  16 + * @var ColorInterface
  17 + */
  18 + private $baseColor;
  19 +
  20 + /**
  21 + * @param int $alpha the alpha value, 0 to 100
  22 + */
  23 + public function __construct(int $alpha, ColorInterface $baseColor)
  24 + {
  25 + if ($alpha < 0 || $alpha > 100) {
  26 + throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100');
  27 + }
  28 +
  29 + $this->alpha = $alpha;
  30 + $this->baseColor = $baseColor;
  31 + }
  32 +
  33 + public function getAlpha() : int
  34 + {
  35 + return $this->alpha;
  36 + }
  37 +
  38 + public function getBaseColor() : ColorInterface
  39 + {
  40 + return $this->baseColor;
  41 + }
  42 +
  43 + public function toRgb() : Rgb
  44 + {
  45 + return $this->baseColor->toRgb();
  46 + }
  47 +
  48 + public function toCmyk() : Cmyk
  49 + {
  50 + return $this->baseColor->toCmyk();
  51 + }
  52 +
  53 + public function toGray() : Gray
  54 + {
  55 + return $this->baseColor->toGray();
  56 + }
  57 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Renderer\Color;
  5 +
  6 +use BaconQrCode\Exception;
  7 +
  8 +final class Cmyk implements ColorInterface
  9 +{
  10 + /**
  11 + * @var int
  12 + */
  13 + private $cyan;
  14 +
  15 + /**
  16 + * @var int
  17 + */
  18 + private $magenta;
  19 +
  20 + /**
  21 + * @var int
  22 + */
  23 + private $yellow;
  24 +
  25 + /**
  26 + * @var int
  27 + */
  28 + private $black;
  29 +
  30 + /**
  31 + * @param int $cyan the cyan amount, 0 to 100
  32 + * @param int $magenta the magenta amount, 0 to 100
  33 + * @param int $yellow the yellow amount, 0 to 100
  34 + * @param int $black the black amount, 0 to 100
  35 + */
  36 + public function __construct(int $cyan, int $magenta, int $yellow, int $black)
  37 + {
  38 + if ($cyan < 0 || $cyan > 100) {
  39 + throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100');
  40 + }
  41 +
  42 + if ($magenta < 0 || $magenta > 100) {
  43 + throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100');
  44 + }
  45 +
  46 + if ($yellow < 0 || $yellow > 100) {
  47 + throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100');
  48 + }
  49 +
  50 + if ($black < 0 || $black > 100) {
  51 + throw new Exception\InvalidArgumentException('Black must be between 0 and 100');
  52 + }
  53 +
  54 + $this->cyan = $cyan;
  55 + $this->magenta = $magenta;
  56 + $this->yellow = $yellow;
  57 + $this->black = $black;
  58 + }
  59 +
  60 + public function getCyan() : int
  61 + {
  62 + return $this->cyan;
  63 + }
  64 +
  65 + public function getMagenta() : int
  66 + {
  67 + return $this->magenta;
  68 + }
  69 +
  70 + public function getYellow() : int
  71 + {
  72 + return $this->yellow;
  73 + }
  74 +
  75 + public function getBlack() : int
  76 + {
  77 + return $this->black;
  78 + }
  79 +
  80 + public function toRgb() : Rgb
  81 + {
  82 + $k = $this->black / 100;
  83 + $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100;
  84 + $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100;
  85 + $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100;
  86 +
  87 + return new Rgb(
  88 + (int) (-$c * 255 + 255),
  89 + (int) (-$m * 255 + 255),
  90 + (int) (-$y * 255 + 255)
  91 + );
  92 + }
  93 +
  94 + public function toCmyk() : Cmyk
  95 + {
  96 + return $this;
  97 + }
  98 +
  99 + public function toGray() : Gray
  100 + {
  101 + return $this->toRgb()->toGray();
  102 + }
  103 +}
  1 +<?php
  2 +declare(strict_types = 1);
  3 +
  4 +namespace BaconQrCode\Renderer\Color;
  5 +
  6 +interface ColorInterface
  7 +{
  8 + /**
  9 + * Converts the color to RGB.
  10 + */
  11 + public function toRgb() : Rgb;
  12 +
  13 + /**
  14 + * Converts the color to CMYK.
  15 + */
  16 + public function toCmyk() : Cmyk;
  17 +
  18 + /**
  19 + * Converts the color to gray.
  20 + */
  21 + public function toGray() : Gray;
  22 +}