setErrorCorrectLevel( \Qrotto\Lib\QR_ERROR_CORRECT_LEVEL_M ); $qr->addData( $text, \Qrotto\Lib\QR_MODE_8BIT_BYTE ); // The library's own capacity table stops at version 10, so count // the bits the way its encoder does: mode, length, then 8 per byte. $data = $qr->getData( 0 ); $type = 0; for ( $t = 1; $t <= 40; $t++ ) { $capacity = 0; foreach ( QRRSBlock::getRSBlocks( $t, \Qrotto\Lib\QR_ERROR_CORRECT_LEVEL_M ) as $block ) { $capacity += $block->getDataCount() * 8; } if ( 4 + $data->getLengthInBits( $t ) + 8 * $data->getLength() <= $capacity ) { $type = $t; break; } } if ( ! $type ) { return null; } $qr->setTypeNumber( $type ); $qr->make(); $n = $qr->getModuleCount(); $grid = array(); for ( $r = 0; $r < $n; $r++ ) { for ( $c = 0; $c < $n; $c++ ) { $grid[ $r ][ $c ] = (bool) $qr->isDark( $r, $c ); } } return $grid; } catch ( \RuntimeException $e ) { return null; } } /** One SVG path: each run of dark modules in a row becomes one rectangle. */ public static function svg( array $grid, $label ) { $n = count( $grid ); $total = $n + 2 * self::QUIET; $d = ''; for ( $r = 0; $r < $n; $r++ ) { $c = 0; while ( $c < $n ) { if ( ! $grid[ $r ][ $c ] ) { ++$c; continue; } $start = $c; while ( $c < $n && $grid[ $r ][ $c ] ) { ++$c; } $d .= 'M' . ( $start + self::QUIET ) . ' ' . ( $r + self::QUIET ) . 'h' . ( $c - $start ) . 'v1h-' . ( $c - $start ) . 'z'; } } return '' . '' . ''; } /** * The scan check: never below 20 mm, and 0.4 mm per module plus the quiet * zone for denser codes. Same rules and words as the badge on qrotto.com. */ public static function min_print_mm( $modules ) { return round( max( 20, ( $modules + 2 * self::QUIET ) * 0.4 ), 1 ); } public static function scan_check( $modules ) { $mm = self::min_print_mm( $modules ); $inches = round( $mm / 25.4, 1 ); /* translators: 1: millimetres, 2: inches */ $size = sprintf( __( '%1$s mm (%2$s in)', 'qrotto' ), $mm, $inches ); $grid = $modules . ' × ' . $modules; if ( $modules <= 33 ) { return array( 'level' => 'is-low', 'title' => __( 'Ready to print', 'qrotto' ), /* translators: 1: size, 2: grid such as 25 × 25 */ 'detail' => sprintf( __( 'Print it at least %1$s wide. Simple code, %2$s.', 'qrotto' ), $size, $grid ) ); } if ( $modules <= 57 ) { return array( 'level' => 'is-mid', 'title' => __( 'Test one copy first', 'qrotto' ), /* translators: 1: size, 2: grid */ 'detail' => sprintf( __( 'Print it at least %1$s wide. Busy code, %2$s.', 'qrotto' ), $size, $grid ) ); } return array( 'level' => 'is-high', 'title' => __( 'Hard to scan when small', 'qrotto' ), /* translators: 1: size, 2: grid */ 'detail' => sprintf( __( 'Print it at least %1$s wide, or shorten the content. Very dense code, %2$s.', 'qrotto' ), $size, $grid ) ); } /** * Full HTML for a code. Problems are shown only to people who can edit; * visitors see nothing rather than a broken code. * * @param array $a Type, field values and display options. * @param bool $preview True in the block editor: adds the scan check. */ public static function code( array $a, $preview = false ) { $type = in_array( $a['type'] ?? 'url', Payload::TYPES, true ) ? $a['type'] : 'url'; if ( 'page' === $type ) { $post = get_post(); $a['page_url'] = $post ? (string) get_permalink( $post ) : home_url( '/' ); } $payload = Payload::build( $type, $a ); $can = current_user_can( 'edit_posts' ); if ( '' !== $payload['error'] ) { return ( $preview || $can ) ? self::notice( $payload['error'] ) : ''; } $grid = self::matrix( $payload['text'] ); if ( null === $grid ) { $msg = __( 'There is too much content for one QR code. Shorten it.', 'qrotto' ); return ( $preview || $can ) ? self::notice( $msg ) : ''; } wp_enqueue_style( 'qrotto' ); $size = max( self::MIN_SIZE, min( self::MAX_SIZE, (int) ( $a['size'] ?? 200 ) ) ); $alt = trim( (string) ( $a['alt'] ?? '' ) ); $alt = '' !== $alt ? $alt : Payload::describe( $type, $a ); $caption = trim( (string) ( $a['caption'] ?? '' ) ); $align = in_array( $a['align'] ?? '', array( 'left', 'center', 'right' ), true ) ? ' qrotto-align-' . $a['align'] : ''; $html = '
'; $svg = self::svg( $grid, $alt ); $html .= $svg; if ( '' !== $caption ) { $html .= '
' . esc_html( $caption ) . '
'; } if ( ! empty( $a['download'] ) ) { $html .= self::download( $svg, $caption, (string) ( $a['download_text'] ?? '' ) ); } if ( Settings::credit_on() ) { $html .= '

' . esc_html__( 'Made with QRotto', 'qrotto' ) . '

'; } $html .= '
'; if ( $preview ) { $check = self::scan_check( count( $grid ) ); $html .= '
' . esc_html( $check['title'] ) . ' ' . esc_html( $check['detail'] ) . '
'; foreach ( $payload['notes'] as $note ) { $html .= '

' . esc_html( $note ) . '

'; } } return $html; } /** * A download link for visitors. Without JavaScript it saves the SVG; the * small download script turns that into a sharp PNG instead. */ private static function download( $svg, $caption, $label ) { wp_enqueue_script( 'qrotto-download' ); $name = sanitize_file_name( strtolower( '' !== $caption ? $caption : 'qr-code' ) ); $name = '' !== $name ? $name : 'qr-code'; $label = '' !== trim( $label ) ? $label : __( 'Download QR code', 'qrotto' ); // esc_url() drops data: links, and this one is base64 we built ourselves. $href = 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- encodes an image, not code return '

' . esc_html( $label ) . '

'; } private static function notice( $message ) { return '
' . esc_html__( 'QRotto', 'qrotto' ) . ' ' . esc_html( $message ) . '
'; } public static function register_style() { wp_register_style( 'qrotto', QROTTO_URL . 'assets/qrotto.css', array(), QROTTO_VERSION ); wp_register_script( 'qrotto-download', QROTTO_URL . 'assets/qrotto-download.js', array(), QROTTO_VERSION, true ); } }