WebOrbiton
v1.3.0

AtheraStudio SyncDrop

280 lines · 9.1 KB
  1. <?php
  2. declare(strict_types=1);
  3. ​
  4. defined('SD_BOOT') || exit;
  5. ​
  6. const SD_QR_TABLE = [
  7. 1 => [26, 10, 1], 2 => [44, 16, 1], 3 => [70, 26, 1], 4 => [100, 18, 2], 5 => [134, 24, 2],
  8. 6 => [172, 16, 4], 7 => [196, 18, 4], 8 => [242, 22, 4], 9 => [292, 22, 5], 10 => [346, 26, 5],
  9. ];
  10. const SD_QR_ALIGN = [
  11. 1 => [], 2 => [6, 18], 3 => [6, 22], 4 => [6, 26], 5 => [6, 30],
  12. 6 => [6, 34], 7 => [6, 22, 38], 8 => [6, 24, 42], 9 => [6, 26, 46], 10 => [6, 28, 50],
  13. ];
  14. ​
  15. function sd_gf_mul(int $x, int $y): int
  16. {
  17. $z = 0;
  18. for ($i = 7; $i >= 0; $i--) {
  19. $z = (($z << 1) ^ (($z >> 7) * 0x11D)) & 0xFF;
  20. if (($y >> $i) & 1) $z ^= $x;
  21. }
  22. return $z;
  23. }
  24. ​
  25. function sd_rs_divisor(int $degree): array
  26. {
  27. $r = array_fill(0, $degree, 0);
  28. $r[$degree - 1] = 1;
  29. $root = 1;
  30. for ($i = 0; $i < $degree; $i++) {
  31. for ($j = 0; $j < $degree; $j++) {
  32. $r[$j] = sd_gf_mul($r[$j], $root);
  33. if ($j + 1 < $degree) $r[$j] ^= $r[$j + 1];
  34. }
  35. $root = sd_gf_mul($root, 0x02);
  36. }
  37. return $r;
  38. }
  39. ​
  40. function sd_rs_remainder(array $data, array $divisor): array
  41. {
  42. $r = array_fill(0, count($divisor), 0);
  43. foreach ($data as $b) {
  44. $factor = $b ^ array_shift($r);
  45. $r[] = 0;
  46. foreach ($divisor as $i => $c) $r[$i] ^= sd_gf_mul($c, $factor);
  47. }
  48. return $r;
  49. }
  50. ​
  51. function sd_qr_codewords(string $text, int &$version): array
  52. {
  53. $len = strlen($text);
  54. $version = 0;
  55. foreach (SD_QR_TABLE as $ver => [$total, $ecc, $blocks]) {
  56. $capacity = ($total - $ecc * $blocks) * 8;
  57. if (4 + ($ver < 10 ? 8 : 16) + 8 * $len <= $capacity) {
  58. $version = $ver;
  59. break;
  60. }
  61. }
  62. if ($version === 0) throw new RuntimeException('qr_too_long');
  63. [$total, $ecc, $numBlocks] = SD_QR_TABLE[$version];
  64. $dataCodewords = $total - $ecc * $numBlocks;
  65. $capacity = $dataCodewords * 8;
  66. ​
  67. $bits = [];
  68. $push = static function (int $value, int $count) use (&$bits): void {
  69. for ($i = $count - 1; $i >= 0; $i--) $bits[] = ($value >> $i) & 1;
  70. };
  71. $push(0b0100, 4);
  72. $push($len, $version < 10 ? 8 : 16);
  73. foreach (str_split($text) as $ch) $push(ord($ch), 8);
  74. $push(0, min(4, $capacity - count($bits)));
  75. $push(0, (8 - count($bits) % 8) % 8);
  76. for ($pad = 0xEC; count($bits) < $capacity; $pad ^= 0xEC ^ 0x11) $push($pad, 8);
  77. ​
  78. $data = [];
  79. foreach (array_chunk($bits, 8) as $byte) {
  80. $v = 0;
  81. foreach ($byte as $bit) $v = ($v << 1) | $bit;
  82. $data[] = $v;
  83. }
  84. ​
  85. $numShort = $numBlocks - $total % $numBlocks;
  86. $shortLen = intdiv($total, $numBlocks);
  87. $divisor = sd_rs_divisor($ecc);
  88. $blocks = [];
  89. $k = 0;
  90. for ($i = 0; $i < $numBlocks; $i++) {
  91. $datLen = $shortLen - $ecc + ($i < $numShort ? 0 : 1);
  92. $dat = array_slice($data, $k, $datLen);
  93. $k += $datLen;
  94. $eccBytes = sd_rs_remainder($dat, $divisor);
  95. if ($i < $numShort) $dat[] = 0;
  96. $blocks[] = array_merge($dat, $eccBytes);
  97. }
  98. $result = [];
  99. $blockLen = count($blocks[0]);
  100. for ($i = 0; $i < $blockLen; $i++) {
  101. foreach ($blocks as $j => $block) {
  102. if ($i !== $shortLen - $ecc || $j >= $numShort) $result[] = $block[$i];
  103. }
  104. }
  105. return $result;
  106. }
  107. ​
  108. function sd_qr_matrix(string $text): array
  109. {
  110. $version = 0;
  111. $codewords = sd_qr_codewords($text, $version);
  112. $size = $version * 4 + 17;
  113. $mod = array_fill(0, $size, array_fill(0, $size, false));
  114. $fn = array_fill(0, $size, array_fill(0, $size, false));
  115. $set = static function (int $x, int $y, bool $dark) use (&$mod, &$fn): void {
  116. $mod[$y][$x] = $dark;
  117. $fn[$y][$x] = true;
  118. };
  119. ​
  120. for ($i = 0; $i < $size; $i++) {
  121. $set(6, $i, $i % 2 === 0);
  122. $set($i, 6, $i % 2 === 0);
  123. }
  124. foreach ([[3, 3], [$size - 4, 3], [3, $size - 4]] as [$cx, $cy]) {
  125. for ($dy = -4; $dy <= 4; $dy++) {
  126. for ($dx = -4; $dx <= 4; $dx++) {
  127. $x = $cx + $dx;
  128. $y = $cy + $dy;
  129. if ($x < 0 || $y < 0 || $x >= $size || $y >= $size) continue;
  130. $d = max(abs($dx), abs($dy));
  131. $set($x, $y, $d !== 2 && $d !== 4);
  132. }
  133. }
  134. }
  135. $align = SD_QR_ALIGN[$version];
  136. $n = count($align);
  137. for ($i = 0; $i < $n; $i++) {
  138. for ($j = 0; $j < $n; $j++) {
  139. if (($i === 0 && $j === 0) || ($i === 0 && $j === $n - 1) || ($i === $n - 1 && $j === 0)) continue;
  140. for ($dy = -2; $dy <= 2; $dy++) {
  141. for ($dx = -2; $dx <= 2; $dx++) {
  142. $set($align[$i] + $dx, $align[$j] + $dy, max(abs($dx), abs($dy)) !== 1);
  143. }
  144. }
  145. }
  146. }
  147. sd_qr_format($mod, $fn, $size, 0);
  148. if ($version >= 7) {
  149. $rem = $version;
  150. for ($i = 0; $i < 12; $i++) $rem = ($rem << 1) ^ (($rem >> 11) * 0x1F25);
  151. $bits = ($version << 12) | $rem;
  152. for ($i = 0; $i < 18; $i++) {
  153. $bit = (($bits >> $i) & 1) === 1;
  154. $a = $size - 11 + $i % 3;
  155. $b = intdiv($i, 3);
  156. $set($a, $b, $bit);
  157. $set($b, $a, $bit);
  158. }
  159. }
  160. ​
  161. $total = count($codewords) * 8;
  162. $i = 0;
  163. for ($right = $size - 1; $right >= 1; $right -= 2) {
  164. if ($right === 6) $right = 5;
  165. for ($vert = 0; $vert < $size; $vert++) {
  166. for ($j = 0; $j < 2; $j++) {
  167. $x = $right - $j;
  168. $upward = (($right + 1) & 2) === 0;
  169. $y = $upward ? $size - 1 - $vert : $vert;
  170. if (!$fn[$y][$x] && $i < $total) {
  171. $mod[$y][$x] = (($codewords[$i >> 3] >> (7 - ($i & 7))) & 1) === 1;
  172. $i++;
  173. }
  174. }
  175. }
  176. }
  177. ​
  178. $best = null;
  179. $bestScore = PHP_INT_MAX;
  180. for ($mask = 0; $mask < 8; $mask++) {
  181. $candidate = sd_qr_mask($mod, $fn, $size, $mask);
  182. sd_qr_format($candidate, $fn, $size, $mask);
  183. $score = sd_qr_penalty($candidate, $size);
  184. if ($score < $bestScore) {
  185. $bestScore = $score;
  186. $best = $candidate;
  187. }
  188. }
  189. return $best;
  190. }
  191. ​
  192. function sd_qr_format(array &$mod, array &$fn, int $size, int $mask): void
  193. {
  194. $data = (0 << 3) | $mask;
  195. $rem = $data;
  196. for ($i = 0; $i < 10; $i++) $rem = ($rem << 1) ^ (($rem >> 9) * 0x537);
  197. $bits = (($data << 10) | $rem) ^ 0x5412;
  198. $bit = static fn(int $i): bool => (($bits >> $i) & 1) === 1;
  199. $put = static function (int $x, int $y, bool $dark) use (&$mod, &$fn): void {
  200. $mod[$y][$x] = $dark;
  201. $fn[$y][$x] = true;
  202. };
  203. for ($i = 0; $i <= 5; $i++) $put(8, $i, $bit($i));
  204. $put(8, 7, $bit(6));
  205. $put(8, 8, $bit(7));
  206. $put(7, 8, $bit(8));
  207. for ($i = 9; $i < 15; $i++) $put(14 - $i, 8, $bit($i));
  208. for ($i = 0; $i < 8; $i++) $put($size - 1 - $i, 8, $bit($i));
  209. for ($i = 8; $i < 15; $i++) $put(8, $size - 15 + $i, $bit($i));
  210. $put(8, $size - 8, true);
  211. }
  212. ​
  213. function sd_qr_mask(array $mod, array $fn, int $size, int $mask): array
  214. {
  215. for ($y = 0; $y < $size; $y++) {
  216. for ($x = 0; $x < $size; $x++) {
  217. if ($fn[$y][$x]) continue;
  218. $invert = match ($mask) {
  219. 0 => ($x + $y) % 2 === 0,
  220. 1 => $y % 2 === 0,
  221. 2 => $x % 3 === 0,
  222. 3 => ($x + $y) % 3 === 0,
  223. 4 => (intdiv($x, 3) + intdiv($y, 2)) % 2 === 0,
  224. 5 => ($x * $y) % 2 + ($x * $y) % 3 === 0,
  225. 6 => (($x * $y) % 2 + ($x * $y) % 3) % 2 === 0,
  226. default => ((($x + $y) % 2) + ($x * $y) % 3) % 2 === 0,
  227. };
  228. if ($invert) $mod[$y][$x] = !$mod[$y][$x];
  229. }
  230. }
  231. return $mod;
  232. }
  233. ​
  234. function sd_qr_penalty(array $mod, int $size): int
  235. {
  236. $score = 0;
  237. $dark = 0;
  238. $lines = [];
  239. for ($y = 0; $y < $size; $y++) {
  240. $row = '';
  241. $col = '';
  242. for ($x = 0; $x < $size; $x++) {
  243. $row .= $mod[$y][$x] ? '1' : '0';
  244. $col .= $mod[$x][$y] ? '1' : '0';
  245. if ($mod[$y][$x]) $dark++;
  246. if ($x < $size - 1 && $y < $size - 1) {
  247. $c = $mod[$y][$x];
  248. if ($c === $mod[$y][$x + 1] && $c === $mod[$y + 1][$x] && $c === $mod[$y + 1][$x + 1]) $score += 3;
  249. }
  250. }
  251. $lines[] = $row;
  252. $lines[] = $col;
  253. }
  254. foreach ($lines as $line) {
  255. if (preg_match_all('/0{5,}|1{5,}/', $line, $m)) {
  256. foreach ($m[0] as $run) $score += 3 + strlen($run) - 5;
  257. }
  258. $score += 40 * (preg_match_all('/(?=10111010000|00001011101)/', $line));
  259. }
  260. $total = $size * $size;
  261. $score += 10 * intdiv(abs($dark * 20 - $total * 10), $total);
  262. return $score;
  263. }
  264. ​
  265. function sd_qr_svg(string $text, int $border = 4): string
  266. {
  267. $mod = sd_qr_matrix($text);
  268. $size = count($mod);
  269. $path = '';
  270. foreach ($mod as $y => $row) {
  271. foreach ($row as $x => $dark) {
  272. if ($dark) $path .= 'M' . ($x + $border) . ' ' . ($y + $border) . 'h1v1h-1z';
  273. }
  274. }
  275. $full = $size + $border * 2;
  276. return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' . $full . ' ' . $full . '" shape-rendering="crispEdges" role="img" class="sd-qr-svg">'
  277. . '<rect width="' . $full . '" height="' . $full . '" fill="#ffffff"/>'
  278. . '<path d="' . $path . '" fill="#000000"/></svg>';
  279. }
  280. ​