26, 2 => 44, 3 => 70, 4 => 100, 5 => 134, 6 => 172, 7 => 196, 8 => 242, 9 => 292, 10 => 346, ); private static $ecc_m = array( 1 => array( 'ec' => 10, 'groups' => array( array( 1, 16 ) ) ), 2 => array( 'ec' => 16, 'groups' => array( array( 1, 28 ) ) ), 3 => array( 'ec' => 26, 'groups' => array( array( 1, 44 ) ) ), 4 => array( 'ec' => 18, 'groups' => array( array( 2, 32 ) ) ), 5 => array( 'ec' => 24, 'groups' => array( array( 2, 43 ) ) ), 6 => array( 'ec' => 16, 'groups' => array( array( 4, 27 ) ) ), 7 => array( 'ec' => 18, 'groups' => array( array( 4, 31 ) ) ), 8 => array( 'ec' => 22, 'groups' => array( array( 2, 38 ), array( 2, 39 ) ) ), 9 => array( 'ec' => 22, 'groups' => array( array( 3, 36 ), array( 2, 37 ) ) ), 10 => array( 'ec' => 26, 'groups' => array( array( 4, 43 ), array( 1, 44 ) ) ), ); private static $align_centres = array( 1 => array(), 2 => array( 6, 18 ), 3 => array( 6, 22 ), 4 => array( 6, 26 ), 5 => array( 6, 30 ), 6 => array( 6, 34 ), 7 => array( 6, 22, 38 ), 8 => array( 6, 24, 42 ), 9 => array( 6, 26, 46 ), 10 => array( 6, 28, 50 ), ); private static $version_info = array( 7 => '000111110010010100', 8 => '001000010110111100', 9 => '001001101010011001', 10 => '001010010011010011', ); private static $format_m = array( 0 => '101010000010010', 1 => '101000100100101', 2 => '101111001111100', 3 => '101101101001011', 4 => '100010111111001', 5 => '100000011001110', 6 => '100111110010111', 7 => '100101010100000', ); private static $gf_exp = null; private static $gf_log = null; public static function svg( $text, array $opts = array() ) { $matrix = self::matrix( $text ); if ( ! $matrix ) { return ''; } $size = isset( $opts['size'] ) ? max( 80, (int) $opts['size'] ) : 200; $margin = isset( $opts['margin'] ) ? max( 0, (int) $opts['margin'] ) : 4; $alt = isset( $opts['alt'] ) ? (string) $opts['alt'] : __( 'QR code', 'ai-eshop-optimizer' ); $n = count( $matrix ); $span = $n + ( $margin * 2 ); $d = ''; for ( $r = 0; $r < $n; $r++ ) { for ( $c = 0; $c < $n; $c++ ) { if ( $matrix[ $r ][ $c ] ) { $d .= 'M' . ( $c + $margin ) . ' ' . ( $r + $margin ) . 'h1v1h-1z'; } } } return sprintf( '' . '' . '' . '', (int) $size, (int) $span, esc_attr( $alt ), esc_attr( $d ) ); } public static function matrix( $text ) { $text = (string) $text; $len = strlen( $text ); if ( 0 === $len ) { return false; } $version = self::pick_version( $len ); if ( ! $version ) { return false; } $codewords = self::encode_codewords( $text, $version ); $size = ( $version * 4 ) + 17; list( $matrix, $reserved ) = self::function_patterns( $version, $size ); $matrix = self::place_codewords( $matrix, $reserved, $codewords, $size ); $best = null; $best_score = PHP_INT_MAX; for ( $mask = 0; $mask < 8; $mask++ ) { $candidate = self::apply_mask( $matrix, $reserved, $mask, $size ); $candidate = self::draw_format( $candidate, $mask, $size ); if ( $version >= 7 ) { $candidate = self::draw_version( $candidate, $version, $size ); } $score = self::penalty( $candidate, $size ); if ( $score < $best_score ) { $best_score = $score; $best = $candidate; } } return $best; } private static function pick_version( $len ) { for ( $v = 1; $v <= 10; $v++ ) { $count_bits = ( $v <= 9 ) ? 8 : 16; $capacity = ( self::data_codewords( $v ) * 8 ) - 4 - $count_bits; if ( $len * 8 <= $capacity ) { return $v; } } return false; } private static function data_codewords( $version ) { $total = 0; foreach ( self::$ecc_m[ $version ]['groups'] as $g ) { $total += $g[0] * $g[1]; } return $total; } private static function encode_codewords( $text, $version ) { $len = strlen( $text ); $count_bits = ( $version <= 9 ) ? 8 : 16; $capacity = self::data_codewords( $version ) * 8; $bits = '0100' . str_pad( decbin( $len ), $count_bits, '0', STR_PAD_LEFT ); for ( $i = 0; $i < $len; $i++ ) { $bits .= str_pad( decbin( ord( $text[ $i ] ) ), 8, '0', STR_PAD_LEFT ); } $bits .= str_repeat( '0', min( 4, $capacity - strlen( $bits ) ) ); if ( strlen( $bits ) % 8 ) { $bits .= str_repeat( '0', 8 - ( strlen( $bits ) % 8 ) ); } $pad = array( 0xEC, 0x11 ); $p = 0; while ( strlen( $bits ) < $capacity ) { $bits .= str_pad( decbin( $pad[ $p % 2 ] ), 8, '0', STR_PAD_LEFT ); $p++; } $data = array(); foreach ( str_split( $bits, 8 ) as $byte ) { $data[] = bindec( $byte ); } $ec_len = self::$ecc_m[ $version ]['ec']; $data_blocks = array(); $ec_blocks = array(); $offset = 0; foreach ( self::$ecc_m[ $version ]['groups'] as $g ) { list( $count, $size ) = $g; for ( $b = 0; $b < $count; $b++ ) { $block = array_slice( $data, $offset, $size ); $offset += $size; $data_blocks[] = $block; $ec_blocks[] = self::reed_solomon( $block, $ec_len ); } } $out = array(); $max = 0; foreach ( $data_blocks as $b ) { $max = max( $max, count( $b ) ); } for ( $i = 0; $i < $max; $i++ ) { foreach ( $data_blocks as $b ) { if ( isset( $b[ $i ] ) ) { $out[] = $b[ $i ]; } } } for ( $i = 0; $i < $ec_len; $i++ ) { foreach ( $ec_blocks as $b ) { $out[] = $b[ $i ]; } } return $out; } private static function init_gf() { if ( null !== self::$gf_exp ) { return; } self::$gf_exp = array_fill( 0, 512, 0 ); self::$gf_log = array_fill( 0, 256, 0 ); $x = 1; for ( $i = 0; $i < 255; $i++ ) { self::$gf_exp[ $i ] = $x; self::$gf_log[ $x ] = $i; $x <<= 1; if ( $x & 0x100 ) { $x ^= 0x11D; } } for ( $i = 255; $i < 512; $i++ ) { self::$gf_exp[ $i ] = self::$gf_exp[ $i - 255 ]; } } private static function gf_mul( $a, $b ) { if ( 0 === $a || 0 === $b ) { return 0; } self::init_gf(); return self::$gf_exp[ self::$gf_log[ $a ] + self::$gf_log[ $b ] ]; } private static function generator( $n ) { self::init_gf(); $gen = array( 1 ); for ( $i = 0; $i < $n; $i++ ) { $alpha = self::$gf_exp[ $i ]; $next = array_fill( 0, count( $gen ) + 1, 0 ); foreach ( $gen as $j => $coef ) { $next[ $j ] ^= $coef; $next[ $j + 1 ] ^= self::gf_mul( $coef, $alpha ); } $gen = $next; } return $gen; } private static function reed_solomon( array $data, $n ) { $gen = self::generator( $n ); $res = array_merge( $data, array_fill( 0, $n, 0 ) ); $len = count( $data ); for ( $i = 0; $i < $len; $i++ ) { $coef = $res[ $i ]; if ( 0 === $coef ) { continue; } foreach ( $gen as $j => $g ) { $res[ $i + $j ] ^= self::gf_mul( $g, $coef ); } } return array_slice( $res, $len, $n ); } private static function function_patterns( $version, $size ) { $m = array_fill( 0, $size, array_fill( 0, $size, 0 ) ); $reserved = array_fill( 0, $size, array_fill( 0, $size, false ) ); foreach ( array( array( 0, 0 ), array( 0, $size - 7 ), array( $size - 7, 0 ) ) as $pos ) { list( $tr, $tc ) = $pos; for ( $r = -1; $r <= 7; $r++ ) { for ( $c = -1; $c <= 7; $c++ ) { $rr = $tr + $r; $cc = $tc + $c; if ( $rr < 0 || $rr >= $size || $cc < 0 || $cc >= $size ) { continue; } $in_ring = ( 0 === $r || 6 === $r || 0 === $c || 6 === $c ) && ( $r >= 0 && $r <= 6 && $c >= 0 && $c <= 6 ); $in_centre = ( $r >= 2 && $r <= 4 && $c >= 2 && $c <= 4 ); $m[ $rr ][ $cc ] = ( $in_ring || $in_centre ) ? 1 : 0; $reserved[ $rr ][ $cc ] = true; } } } for ( $i = 8; $i < $size - 8; $i++ ) { $bit = ( 0 === $i % 2 ) ? 1 : 0; $m[ 6 ][ $i ] = $bit; $reserved[ 6 ][ $i ] = true; $m[ $i ][ 6 ] = $bit; $reserved[ $i ][ 6 ] = true; } $centres = self::$align_centres[ $version ]; $last = $size - 7; foreach ( $centres as $cr ) { foreach ( $centres as $cc ) { if ( ( 6 === $cr && 6 === $cc ) || ( 6 === $cr && $cc === $last ) || ( $cr === $last && 6 === $cc ) ) { continue; } for ( $r = -2; $r <= 2; $r++ ) { for ( $c = -2; $c <= 2; $c++ ) { $rr = $cr + $r; $cc2 = $cc + $c; $dark = ( 2 === max( abs( $r ), abs( $c ) ) ) || ( 0 === $r && 0 === $c ); $m[ $rr ][ $cc2 ] = $dark ? 1 : 0; $reserved[ $rr ][ $cc2 ] = true; } } } } for ( $i = 0; $i < 9; $i++ ) { $reserved[ 8 ][ $i ] = true; $reserved[ $i ][ 8 ] = true; } for ( $i = 0; $i < 8; $i++ ) { $reserved[ 8 ][ $size - 1 - $i ] = true; $reserved[ $size - 1 - $i ][ 8 ] = true; } $m[ $size - 8 ][ 8 ] = 1; $reserved[ $size - 8 ][ 8 ] = true; if ( $version >= 7 ) { for ( $i = 0; $i < 18; $i++ ) { $a = $size - 11 + ( $i % 3 ); $b = intdiv( $i, 3 ); $reserved[ $b ][ $a ] = true; $reserved[ $a ][ $b ] = true; } } return array( $m, $reserved ); } private static function place_codewords( array $m, array $reserved, array $codewords, $size ) { $bits = ''; foreach ( $codewords as $cw ) { $bits .= str_pad( decbin( $cw ), 8, '0', STR_PAD_LEFT ); } $total = strlen( $bits ); $i = 0; $row = $size - 1; $dir = -1; for ( $col = $size - 1; $col > 0; $col -= 2 ) { if ( 6 === $col ) { $col--; } while ( true ) { for ( $c = 0; $c < 2; $c++ ) { $cc = $col - $c; if ( ! $reserved[ $row ][ $cc ] ) { $m[ $row ][ $cc ] = ( $i < $total ) ? (int) $bits[ $i ] : 0; $i++; } } $row += $dir; if ( $row < 0 || $row >= $size ) { $row -= $dir; $dir = -$dir; break; } } } return $m; } private static function apply_mask( array $m, array $reserved, $mask, $size ) { for ( $r = 0; $r < $size; $r++ ) { for ( $c = 0; $c < $size; $c++ ) { if ( $reserved[ $r ][ $c ] ) { continue; } switch ( $mask ) { case 0: $flip = ( 0 === ( $r + $c ) % 2 ); break; case 1: $flip = ( 0 === $r % 2 ); break; case 2: $flip = ( 0 === $c % 3 ); break; case 3: $flip = ( 0 === ( $r + $c ) % 3 ); break; case 4: $flip = ( 0 === ( intdiv( $r, 2 ) + intdiv( $c, 3 ) ) % 2 ); break; case 5: $flip = ( 0 === ( ( $r * $c ) % 2 ) + ( ( $r * $c ) % 3 ) ); break; case 6: $flip = ( 0 === ( ( ( $r * $c ) % 2 ) + ( ( $r * $c ) % 3 ) ) % 2 ); break; default: $flip = ( 0 === ( ( ( $r + $c ) % 2 ) + ( ( $r * $c ) % 3 ) ) % 2 ); break; } if ( $flip ) { $m[ $r ][ $c ] ^= 1; } } } return $m; } private static function draw_format( array $m, $mask, $size ) { $bits = self::$format_m[ $mask ]; for ( $i = 0; $i < 15; $i++ ) { $bit = (int) $bits[ 14 - $i ]; if ( $i < 6 ) { $m[ $i ][ 8 ] = $bit; } elseif ( 6 === $i ) { $m[7][8] = $bit; } elseif ( 7 === $i ) { $m[8][8] = $bit; } elseif ( 8 === $i ) { $m[8][7] = $bit; } else { $m[8][ 14 - $i ] = $bit; } if ( $i < 8 ) { $m[8][ $size - 1 - $i ] = $bit; } else { $m[ $size - 15 + $i ][8] = $bit; } } $m[ $size - 8 ][8] = 1; return $m; } private static function draw_version( array $m, $version, $size ) { $bits = self::$version_info[ $version ]; for ( $i = 0; $i < 18; $i++ ) { $bit = (int) $bits[ 17 - $i ]; $a = $size - 11 + ( $i % 3 ); $b = intdiv( $i, 3 ); $m[ $b ][ $a ] = $bit; $m[ $a ][ $b ] = $bit; } return $m; } private static function penalty( array $m, $size ) { $score = 0; for ( $r = 0; $r < $size; $r++ ) { $run = 1; for ( $c = 1; $c < $size; $c++ ) { if ( $m[ $r ][ $c ] === $m[ $r ][ $c - 1 ] ) { $run++; } else { if ( $run >= 5 ) { $score += 3 + ( $run - 5 ); } $run = 1; } } if ( $run >= 5 ) { $score += 3 + ( $run - 5 ); } } for ( $c = 0; $c < $size; $c++ ) { $run = 1; for ( $r = 1; $r < $size; $r++ ) { if ( $m[ $r ][ $c ] === $m[ $r - 1 ][ $c ] ) { $run++; } else { if ( $run >= 5 ) { $score += 3 + ( $run - 5 ); } $run = 1; } } if ( $run >= 5 ) { $score += 3 + ( $run - 5 ); } } for ( $r = 0; $r < $size - 1; $r++ ) { for ( $c = 0; $c < $size - 1; $c++ ) { $v = $m[ $r ][ $c ]; if ( $v === $m[ $r ][ $c + 1 ] && $v === $m[ $r + 1 ][ $c ] && $v === $m[ $r + 1 ][ $c + 1 ] ) { $score += 3; } } } $a = '10111010000'; $b = '00001011101'; for ( $r = 0; $r < $size; $r++ ) { $line = ''; for ( $c = 0; $c < $size; $c++ ) { $line .= $m[ $r ][ $c ]; } $score += 40 * ( substr_count( $line, $a ) + substr_count( $line, $b ) ); } for ( $c = 0; $c < $size; $c++ ) { $line = ''; for ( $r = 0; $r < $size; $r++ ) { $line .= $m[ $r ][ $c ]; } $score += 40 * ( substr_count( $line, $a ) + substr_count( $line, $b ) ); } $dark = 0; for ( $r = 0; $r < $size; $r++ ) { $dark += array_sum( $m[ $r ] ); } $percent = ( $dark * 100 ) / ( $size * $size ); $score += 10 * (int) floor( abs( $percent - 50 ) / 5 ); return $score; } }