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 = '
' . 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 ''; } private static function notice( $message ) { return '