v1.3.0
AtheraStudio SyncDrop
- <?php
- declare(strict_types=1);
-
- defined('SD_BOOT') || exit;
-
- const SD_QR_TABLE = [
- 1 => [26, 10, 1], 2 => [44, 16, 1], 3 => [70, 26, 1], 4 => [100, 18, 2], 5 => [134, 24, 2],
- 6 => [172, 16, 4], 7 => [196, 18, 4], 8 => [242, 22, 4], 9 => [292, 22, 5], 10 => [346, 26, 5],
- ];
- const SD_QR_ALIGN = [
- 1 => [], 2 => [6, 18], 3 => [6, 22], 4 => [6, 26], 5 => [6, 30],
- 6 => [6, 34], 7 => [6, 22, 38], 8 => [6, 24, 42], 9 => [6, 26, 46], 10 => [6, 28, 50],
- ];
-
- function sd_gf_mul(int $x, int $y): int
- {
- $z = 0;
- for ($i = 7; $i >= 0; $i--) {
- $z = (($z << 1) ^ (($z >> 7) * 0x11D)) & 0xFF;
- if (($y >> $i) & 1) $z ^= $x;
- }
- return $z;
- }
-
- function sd_rs_divisor(int $degree): array
- {
- $r = array_fill(0, $degree, 0);
- $r[$degree - 1] = 1;
- $root = 1;
- for ($i = 0; $i < $degree; $i++) {
- for ($j = 0; $j < $degree; $j++) {
- $r[$j] = sd_gf_mul($r[$j], $root);
- if ($j + 1 < $degree) $r[$j] ^= $r[$j + 1];
- }
- $root = sd_gf_mul($root, 0x02);
- }
- return $r;
- }
-
- function sd_rs_remainder(array $data, array $divisor): array
- {
- $r = array_fill(0, count($divisor), 0);
- foreach ($data as $b) {
- $factor = $b ^ array_shift($r);
- $r[] = 0;
- foreach ($divisor as $i => $c) $r[$i] ^= sd_gf_mul($c, $factor);
- }
- return $r;
- }
-
- function sd_qr_codewords(string $text, int &$version): array
- {
- $len = strlen($text);
- $version = 0;
- foreach (SD_QR_TABLE as $ver => [$total, $ecc, $blocks]) {
- $capacity = ($total - $ecc * $blocks) * 8;
- if (4 + ($ver < 10 ? 8 : 16) + 8 * $len <= $capacity) {
- $version = $ver;
- break;
- }
- }
- if ($version === 0) throw new RuntimeException('qr_too_long');
- [$total, $ecc, $numBlocks] = SD_QR_TABLE[$version];
- $dataCodewords = $total - $ecc * $numBlocks;
- $capacity = $dataCodewords * 8;
-
- $bits = [];
- $push = static function (int $value, int $count) use (&$bits): void {
- for ($i = $count - 1; $i >= 0; $i--) $bits[] = ($value >> $i) & 1;
- };
- $push(0b0100, 4);
- $push($len, $version < 10 ? 8 : 16);
- foreach (str_split($text) as $ch) $push(ord($ch), 8);
- $push(0, min(4, $capacity - count($bits)));
- $push(0, (8 - count($bits) % 8) % 8);
- for ($pad = 0xEC; count($bits) < $capacity; $pad ^= 0xEC ^ 0x11) $push($pad, 8);
-
- $data = [];
- foreach (array_chunk($bits, 8) as $byte) {
- $v = 0;
- foreach ($byte as $bit) $v = ($v << 1) | $bit;
- $data[] = $v;
- }
-
- $numShort = $numBlocks - $total % $numBlocks;
- $shortLen = intdiv($total, $numBlocks);
- $divisor = sd_rs_divisor($ecc);
- $blocks = [];
- $k = 0;
- for ($i = 0; $i < $numBlocks; $i++) {
- $datLen = $shortLen - $ecc + ($i < $numShort ? 0 : 1);
- $dat = array_slice($data, $k, $datLen);
- $k += $datLen;
- $eccBytes = sd_rs_remainder($dat, $divisor);
- if ($i < $numShort) $dat[] = 0;
- $blocks[] = array_merge($dat, $eccBytes);
- }
- $result = [];
- $blockLen = count($blocks[0]);
- for ($i = 0; $i < $blockLen; $i++) {
- foreach ($blocks as $j => $block) {
- if ($i !== $shortLen - $ecc || $j >= $numShort) $result[] = $block[$i];
- }
- }
- return $result;
- }
-
- function sd_qr_matrix(string $text): array
- {
- $version = 0;
- $codewords = sd_qr_codewords($text, $version);
- $size = $version * 4 + 17;
- $mod = array_fill(0, $size, array_fill(0, $size, false));
- $fn = array_fill(0, $size, array_fill(0, $size, false));
- $set = static function (int $x, int $y, bool $dark) use (&$mod, &$fn): void {
- $mod[$y][$x] = $dark;
- $fn[$y][$x] = true;
- };
-
- for ($i = 0; $i < $size; $i++) {
- $set(6, $i, $i % 2 === 0);
- $set($i, 6, $i % 2 === 0);
- }
- foreach ([[3, 3], [$size - 4, 3], [3, $size - 4]] as [$cx, $cy]) {
- for ($dy = -4; $dy <= 4; $dy++) {
- for ($dx = -4; $dx <= 4; $dx++) {
- $x = $cx + $dx;
- $y = $cy + $dy;
- if ($x < 0 || $y < 0 || $x >= $size || $y >= $size) continue;
- $d = max(abs($dx), abs($dy));
- $set($x, $y, $d !== 2 && $d !== 4);
- }
- }
- }
- $align = SD_QR_ALIGN[$version];
- $n = count($align);
- for ($i = 0; $i < $n; $i++) {
- for ($j = 0; $j < $n; $j++) {
- if (($i === 0 && $j === 0) || ($i === 0 && $j === $n - 1) || ($i === $n - 1 && $j === 0)) continue;
- for ($dy = -2; $dy <= 2; $dy++) {
- for ($dx = -2; $dx <= 2; $dx++) {
- $set($align[$i] + $dx, $align[$j] + $dy, max(abs($dx), abs($dy)) !== 1);
- }
- }
- }
- }
- sd_qr_format($mod, $fn, $size, 0);
- if ($version >= 7) {
- $rem = $version;
- for ($i = 0; $i < 12; $i++) $rem = ($rem << 1) ^ (($rem >> 11) * 0x1F25);
- $bits = ($version << 12) | $rem;
- for ($i = 0; $i < 18; $i++) {
- $bit = (($bits >> $i) & 1) === 1;
- $a = $size - 11 + $i % 3;
- $b = intdiv($i, 3);
- $set($a, $b, $bit);
- $set($b, $a, $bit);
- }
- }
-
- $total = count($codewords) * 8;
- $i = 0;
- for ($right = $size - 1; $right >= 1; $right -= 2) {
- if ($right === 6) $right = 5;
- for ($vert = 0; $vert < $size; $vert++) {
- for ($j = 0; $j < 2; $j++) {
- $x = $right - $j;
- $upward = (($right + 1) & 2) === 0;
- $y = $upward ? $size - 1 - $vert : $vert;
- if (!$fn[$y][$x] && $i < $total) {
- $mod[$y][$x] = (($codewords[$i >> 3] >> (7 - ($i & 7))) & 1) === 1;
- $i++;
- }
- }
- }
- }
-
- $best = null;
- $bestScore = PHP_INT_MAX;
- for ($mask = 0; $mask < 8; $mask++) {
- $candidate = sd_qr_mask($mod, $fn, $size, $mask);
- sd_qr_format($candidate, $fn, $size, $mask);
- $score = sd_qr_penalty($candidate, $size);
- if ($score < $bestScore) {
- $bestScore = $score;
- $best = $candidate;
- }
- }
- return $best;
- }
-
- function sd_qr_format(array &$mod, array &$fn, int $size, int $mask): void
- {
- $data = (0 << 3) | $mask;
- $rem = $data;
- for ($i = 0; $i < 10; $i++) $rem = ($rem << 1) ^ (($rem >> 9) * 0x537);
- $bits = (($data << 10) | $rem) ^ 0x5412;
- $bit = static fn(int $i): bool => (($bits >> $i) & 1) === 1;
- $put = static function (int $x, int $y, bool $dark) use (&$mod, &$fn): void {
- $mod[$y][$x] = $dark;
- $fn[$y][$x] = true;
- };
- for ($i = 0; $i <= 5; $i++) $put(8, $i, $bit($i));
- $put(8, 7, $bit(6));
- $put(8, 8, $bit(7));
- $put(7, 8, $bit(8));
- for ($i = 9; $i < 15; $i++) $put(14 - $i, 8, $bit($i));
- for ($i = 0; $i < 8; $i++) $put($size - 1 - $i, 8, $bit($i));
- for ($i = 8; $i < 15; $i++) $put(8, $size - 15 + $i, $bit($i));
- $put(8, $size - 8, true);
- }
-
- function sd_qr_mask(array $mod, array $fn, int $size, int $mask): array
- {
- for ($y = 0; $y < $size; $y++) {
- for ($x = 0; $x < $size; $x++) {
- if ($fn[$y][$x]) continue;
- $invert = match ($mask) {
- 0 => ($x + $y) % 2 === 0,
- 1 => $y % 2 === 0,
- 2 => $x % 3 === 0,
- 3 => ($x + $y) % 3 === 0,
- 4 => (intdiv($x, 3) + intdiv($y, 2)) % 2 === 0,
- 5 => ($x * $y) % 2 + ($x * $y) % 3 === 0,
- 6 => (($x * $y) % 2 + ($x * $y) % 3) % 2 === 0,
- default => ((($x + $y) % 2) + ($x * $y) % 3) % 2 === 0,
- };
- if ($invert) $mod[$y][$x] = !$mod[$y][$x];
- }
- }
- return $mod;
- }
-
- function sd_qr_penalty(array $mod, int $size): int
- {
- $score = 0;
- $dark = 0;
- $lines = [];
- for ($y = 0; $y < $size; $y++) {
- $row = '';
- $col = '';
- for ($x = 0; $x < $size; $x++) {
- $row .= $mod[$y][$x] ? '1' : '0';
- $col .= $mod[$x][$y] ? '1' : '0';
- if ($mod[$y][$x]) $dark++;
- if ($x < $size - 1 && $y < $size - 1) {
- $c = $mod[$y][$x];
- if ($c === $mod[$y][$x + 1] && $c === $mod[$y + 1][$x] && $c === $mod[$y + 1][$x + 1]) $score += 3;
- }
- }
- $lines[] = $row;
- $lines[] = $col;
- }
- foreach ($lines as $line) {
- if (preg_match_all('/0{5,}|1{5,}/', $line, $m)) {
- foreach ($m[0] as $run) $score += 3 + strlen($run) - 5;
- }
- $score += 40 * (preg_match_all('/(?=10111010000|00001011101)/', $line));
- }
- $total = $size * $size;
- $score += 10 * intdiv(abs($dark * 20 - $total * 10), $total);
- return $score;
- }
-
- function sd_qr_svg(string $text, int $border = 4): string
- {
- $mod = sd_qr_matrix($text);
- $size = count($mod);
- $path = '';
- foreach ($mod as $y => $row) {
- foreach ($row as $x => $dark) {
- if ($dark) $path .= 'M' . ($x + $border) . ' ' . ($y + $border) . 'h1v1h-1z';
- }
- }
- $full = $size + $border * 2;
- return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' . $full . ' ' . $full . '" shape-rendering="crispEdges" role="img" class="sd-qr-svg">'
- . '<rect width="' . $full . '" height="' . $full . '" fill="#ffffff"/>'
- . '<path d="' . $path . '" fill="#000000"/></svg>';
- }
-