$width_modules ) { if ( $is_bar ) { $x = $offset * $args['scale'] + $args['padding']; $w = $width_modules * $args['scale']; $h = ( ! empty( $guard[ $index ] ) && $args['text'] ) ? $guard_height : $bar_height; $parts[] = sprintf( '', self::num( $x ), self::num( $bar_top ), self::num( $w ), self::num( $h ), $args['bar_color'] ); } $offset += $width_modules; $is_bar = ! $is_bar; } if ( $args['text'] ) { $parts[] = self::text_layer( $symbol, $args, $quiet, $bar_top + $bar_height ); } $attributes = array( 'xmlns' => 'http://www.w3.org/2000/svg', 'viewBox' => '0 0 ' . self::num( $width ) . ' ' . self::num( $height ), 'width' => self::num( $width ), 'height' => self::num( $height ), 'role' => 'img', 'aria-label' => $label, 'preserveAspectRatio' => 'xMidYMid meet', 'shape-rendering' => 'crispEdges', ); if ( '' !== $args['class'] ) { $attributes['class'] = $args['class']; } if ( 'transparent' !== $args['bg_color'] ) { array_unshift( $parts, sprintf( '', self::num( $width ), self::num( $height ), $args['bg_color'] ) ); } $markup = ' $value ) { $markup .= ' ' . $name . '="' . esc_attr( $value ) . '"'; } $markup .= '>' . esc_html( $label ) . ''; $markup .= implode( '', $parts ); $markup .= ''; return $markup; } /** * Build the human readable digit layer. * * @param array $symbol Encoded symbol. * @param array $args Parsed settings. * @param array $quiet Quiet zone widths in modules. * @param float $bar_bottom Y offset of the bottom of the bars. * @return string */ protected static function text_layer( $symbol, $args, $quiet, $bar_bottom ) { $families = isset( $symbol['groups'] ) ? $symbol['groups'] : array(); $baseline = $bar_bottom + $args['text_size']; $style = sprintf( 'font-family:%s;font-size:%spx;fill:%s;', $args['font_family'], self::num( $args['text_size'] ), $args['text_color'] ); $out = ''; if ( $symbol['lead'] !== '' ) { $out .= sprintf( '%s', self::num( $args['padding'] + $quiet[0] * $args['scale'] / 2 ), self::num( $baseline ), $style, esc_html( $symbol['lead'] ) ); } foreach ( $families as $group ) { $center = ( $quiet[0] + ( $group['from'] + $group['to'] + 1 ) / 2 ) * $args['scale'] + $args['padding']; $out .= sprintf( '%s', self::num( $center ), self::num( $baseline ), $style, esc_html( $group['text'] ) ); } if ( $symbol['tail'] !== '' ) { $out .= sprintf( '%s', self::num( $args['padding'] + ( $quiet[0] + array_sum( $symbol['elements'] ) + $quiet[1] / 2 ) * $args['scale'] ), self::num( $baseline ), $style, esc_html( $symbol['tail'] ) ); } if ( ! $families && '' === $symbol['lead'] && '' === $symbol['tail'] ) { $out .= sprintf( '%s', self::num( $args['padding'] + ( $quiet[0] + array_sum( $symbol['elements'] ) / 2 ) * $args['scale'] ), self::num( $baseline ), $style, esc_html( $symbol['text'] ) ); } return $out; } /** * Merge user settings with defaults and validate every value. * * @param array $args Raw settings. * @return array */ public static function parse_args( $args ) { $defaults = array( 'scale' => 2, 'height' => 60, 'text' => true, 'text_size' => 14, 'bar_color' => '#000000', 'bg_color' => 'transparent', 'text_color' => '', 'padding' => 0, 'class' => 'bcgn-barcode', 'font_family' => 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace', ); $args = array_merge( $defaults, array_intersect_key( $args, $defaults ) ); $args['scale'] = max( 0.5, min( 10, (float) $args['scale'] ) ); $args['height'] = (int) max( 10, min( 400, (int) $args['height'] ) ); $args['text_size'] = (int) max( 6, min( 48, (int) $args['text_size'] ) ); $args['padding'] = (int) max( 0, min( 100, (int) $args['padding'] ) ); $args['text'] = self::truthy( $args['text'] ); $args['bar_color'] = self::color( $args['bar_color'], '#000000' ); $args['bg_color'] = self::color( $args['bg_color'], 'transparent' ); $args['class'] = self::class_names( $args['class'] ); if ( '' === $args['text_color'] ) { $args['text_color'] = $args['bar_color']; } else { $args['text_color'] = self::color( $args['text_color'], $args['bar_color'] ); } return $args; } /** * Interpret common truthy values coming from shortcode attributes. * * @param mixed $value Raw value. * @return bool */ protected static function truthy( $value ) { if ( is_bool( $value ) ) { return $value; } return ! in_array( strtolower( (string) $value ), array( '', '0', 'false', 'no', 'off' ), true ); } /** * Validate a colour, falling back to a safe default. * * @param mixed $value Raw colour. * @param string $fallback Fallback colour. * @return string */ public static function color( $value, $fallback ) { $value = trim( (string) $value ); if ( 'transparent' === $value || 'none' === $value ) { return 'transparent'; } if ( preg_match( '/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value ) ) { return strtolower( $value ); } if ( preg_match( '/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*(0|1|0?\.\d+)\s*)?\)$/', $value ) ) { return preg_replace( '/\s+/', '', strtolower( $value ) ); } if ( preg_match( '/^[a-z]{3,20}$/i', $value ) ) { return strtolower( $value ); } return $fallback; } /** * Keep CSS class names to a safe subset. * * @param mixed $value Raw class list. * @return string */ protected static function class_names( $value ) { $names = preg_split( '/\s+/', trim( (string) $value ) ); $clean = array(); foreach ( (array) $names as $name ) { if ( preg_match( '/^-?[_a-zA-Z][_a-zA-Z0-9-]*$/', $name ) ) { $clean[] = $name; } } return implode( ' ', array_slice( $clean, 0, 5 ) ); } /** * Human readable label for a format slug. * * @param string $format Format slug. * @return string */ protected static function format_label( $format ) { $formats = BCGN_Barcode::formats(); if ( isset( $formats[ $format ] ) ) { return $formats[ $format ]['label']; } return strtoupper( $format ); } /** * Format a number without trailing zeros. * * @param float $value Number to format. * @return string */ protected static function num( $value ) { $value = round( (float) $value, 3 ); $text = number_format( $value, 3, '.', '' ); return rtrim( rtrim( $text, '0' ), '.' ); } }