wp/wp-includes/block-supports/duotone.php
changeset 19 3d72ae0968f4
parent 18 be944660c56a
child 21 48c4eec2b7e6
equal deleted inserted replaced
18:be944660c56a 19:3d72ae0968f4
    43  * @since 5.8.0
    43  * @since 5.8.0
    44  * @access private
    44  * @access private
    45  *
    45  *
    46  * @param mixed $n   Number of unknown type.
    46  * @param mixed $n   Number of unknown type.
    47  * @param int   $max Upper value of the range to bound to.
    47  * @param int   $max Upper value of the range to bound to.
    48  *
       
    49  * @return float Value in the range [0, 1].
    48  * @return float Value in the range [0, 1].
    50  */
    49  */
    51 function wp_tinycolor_bound01( $n, $max ) {
    50 function wp_tinycolor_bound01( $n, $max ) {
    52 	if ( 'string' === gettype( $n ) && false !== strpos( $n, '.' ) && 1 === (float) $n ) {
    51 	if ( 'string' === gettype( $n ) && false !== strpos( $n, '.' ) && 1 === (float) $n ) {
    53 		$n = '100%';
    52 		$n = '100%';
    68 	// Convert into [0, 1] range if it isn't already.
    67 	// Convert into [0, 1] range if it isn't already.
    69 	return ( $n % $max ) / (float) $max;
    68 	return ( $n % $max ) / (float) $max;
    70 }
    69 }
    71 
    70 
    72 /**
    71 /**
    73  * Round and convert values of an RGB object.
    72  * Direct port of tinycolor's boundAlpha function to maintain consistency with
       
    73  * how tinycolor works.
       
    74  *
       
    75  * @see https://github.com/bgrins/TinyColor
       
    76  *
       
    77  * @since 5.9.0
       
    78  * @access private
       
    79  *
       
    80  * @param mixed $n Number of unknown type.
       
    81  * @return float Value in the range [0,1].
       
    82  */
       
    83 function _wp_tinycolor_bound_alpha( $n ) {
       
    84 	if ( is_numeric( $n ) ) {
       
    85 		$n = (float) $n;
       
    86 		if ( $n >= 0 && $n <= 1 ) {
       
    87 			return $n;
       
    88 		}
       
    89 	}
       
    90 	return 1;
       
    91 }
       
    92 
       
    93 /**
       
    94  * Rounds and converts values of an RGB object.
    74  *
    95  *
    75  * Direct port of TinyColor's function, lightly simplified to maintain
    96  * Direct port of TinyColor's function, lightly simplified to maintain
    76  * consistency with TinyColor.
    97  * consistency with TinyColor.
    77  *
    98  *
    78  * @see https://github.com/bgrins/TinyColor
    99  * @see https://github.com/bgrins/TinyColor
    79  *
   100  *
    80  * @since 5.8.0
   101  * @since 5.8.0
    81  * @access private
   102  * @access private
    82  *
   103  *
    83  * @param array $rgb_color RGB object.
   104  * @param array $rgb_color RGB object.
    84  *
       
    85  * @return array Rounded and converted RGB object.
   105  * @return array Rounded and converted RGB object.
    86  */
   106  */
    87 function wp_tinycolor_rgb_to_rgb( $rgb_color ) {
   107 function wp_tinycolor_rgb_to_rgb( $rgb_color ) {
    88 	return array(
   108 	return array(
    89 		'r' => wp_tinycolor_bound01( $rgb_color['r'], 255 ) * 255,
   109 		'r' => wp_tinycolor_bound01( $rgb_color['r'], 255 ) * 255,
   104  * @access private
   124  * @access private
   105  *
   125  *
   106  * @param float $p first component.
   126  * @param float $p first component.
   107  * @param float $q second component.
   127  * @param float $q second component.
   108  * @param float $t third component.
   128  * @param float $t third component.
   109  *
       
   110  * @return float R, G, or B component.
   129  * @return float R, G, or B component.
   111  */
   130  */
   112 function wp_tinycolor_hue_to_rgb( $p, $q, $t ) {
   131 function wp_tinycolor_hue_to_rgb( $p, $q, $t ) {
   113 	if ( $t < 0 ) {
   132 	if ( $t < 0 ) {
   114 		$t += 1;
   133 		$t += 1;
   127 	}
   146 	}
   128 	return $p;
   147 	return $p;
   129 }
   148 }
   130 
   149 
   131 /**
   150 /**
   132  * Convert an HSL object to an RGB object with converted and rounded values.
   151  * Converts an HSL object to an RGB object with converted and rounded values.
   133  *
   152  *
   134  * Direct port of TinyColor's function, lightly simplified to maintain
   153  * Direct port of TinyColor's function, lightly simplified to maintain
   135  * consistency with TinyColor.
   154  * consistency with TinyColor.
   136  *
   155  *
   137  * @see https://github.com/bgrins/TinyColor
   156  * @see https://github.com/bgrins/TinyColor
   138  *
   157  *
   139  * @since 5.8.0
   158  * @since 5.8.0
   140  * @access private
   159  * @access private
   141  *
   160  *
   142  * @param array $hsl_color HSL object.
   161  * @param array $hsl_color HSL object.
   143  *
       
   144  * @return array Rounded and converted RGB object.
   162  * @return array Rounded and converted RGB object.
   145  */
   163  */
   146 function wp_tinycolor_hsl_to_rgb( $hsl_color ) {
   164 function wp_tinycolor_hsl_to_rgb( $hsl_color ) {
   147 	$h = wp_tinycolor_bound01( $hsl_color['h'], 360 );
   165 	$h = wp_tinycolor_bound01( $hsl_color['h'], 360 );
   148 	$s = wp_tinycolor_bound01( $hsl_color['s'], 100 );
   166 	$s = wp_tinycolor_bound01( $hsl_color['s'], 100 );
   168 	);
   186 	);
   169 }
   187 }
   170 
   188 
   171 /**
   189 /**
   172  * Parses hex, hsl, and rgb CSS strings using the same regex as TinyColor v1.4.2
   190  * Parses hex, hsl, and rgb CSS strings using the same regex as TinyColor v1.4.2
   173  * used in the JavaScript. Only colors output from react-color are implemented
   191  * used in the JavaScript. Only colors output from react-color are implemented.
   174  * and the alpha value is ignored as it is not used in duotone.
       
   175  *
   192  *
   176  * Direct port of TinyColor's function, lightly simplified to maintain
   193  * Direct port of TinyColor's function, lightly simplified to maintain
   177  * consistency with TinyColor.
   194  * consistency with TinyColor.
   178  *
   195  *
   179  * @see https://github.com/bgrins/TinyColor
   196  * @see https://github.com/bgrins/TinyColor
   180  * @see https://github.com/casesandberg/react-color/
   197  * @see https://github.com/casesandberg/react-color/
   181  *
   198  *
   182  * @since 5.8.0
   199  * @since 5.8.0
       
   200  * @since 5.9.0 Added alpha processing.
   183  * @access private
   201  * @access private
   184  *
   202  *
   185  * @param string $color_str CSS color string.
   203  * @param string $color_str CSS color string.
   186  *
       
   187  * @return array RGB object.
   204  * @return array RGB object.
   188  */
   205  */
   189 function wp_tinycolor_string_to_rgb( $color_str ) {
   206 function wp_tinycolor_string_to_rgb( $color_str ) {
   190 	$color_str = strtolower( trim( $color_str ) );
   207 	$color_str = strtolower( trim( $color_str ) );
   191 
   208 
   197 	$permissive_match3 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?';
   214 	$permissive_match3 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?';
   198 	$permissive_match4 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?';
   215 	$permissive_match4 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?';
   199 
   216 
   200 	$rgb_regexp = '/^rgb' . $permissive_match3 . '$/';
   217 	$rgb_regexp = '/^rgb' . $permissive_match3 . '$/';
   201 	if ( preg_match( $rgb_regexp, $color_str, $match ) ) {
   218 	if ( preg_match( $rgb_regexp, $color_str, $match ) ) {
   202 		return wp_tinycolor_rgb_to_rgb(
   219 		$rgb = wp_tinycolor_rgb_to_rgb(
   203 			array(
   220 			array(
   204 				'r' => $match[1],
   221 				'r' => $match[1],
   205 				'g' => $match[2],
   222 				'g' => $match[2],
   206 				'b' => $match[3],
   223 				'b' => $match[3],
   207 			)
   224 			)
   208 		);
   225 		);
       
   226 
       
   227 		$rgb['a'] = 1;
       
   228 
       
   229 		return $rgb;
   209 	}
   230 	}
   210 
   231 
   211 	$rgba_regexp = '/^rgba' . $permissive_match4 . '$/';
   232 	$rgba_regexp = '/^rgba' . $permissive_match4 . '$/';
   212 	if ( preg_match( $rgba_regexp, $color_str, $match ) ) {
   233 	if ( preg_match( $rgba_regexp, $color_str, $match ) ) {
   213 		return wp_tinycolor_rgb_to_rgb(
   234 		$rgb = wp_tinycolor_rgb_to_rgb(
   214 			array(
   235 			array(
   215 				'r' => $match[1],
   236 				'r' => $match[1],
   216 				'g' => $match[2],
   237 				'g' => $match[2],
   217 				'b' => $match[3],
   238 				'b' => $match[3],
   218 			)
   239 			)
   219 		);
   240 		);
       
   241 
       
   242 		$rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );
       
   243 
       
   244 		return $rgb;
   220 	}
   245 	}
   221 
   246 
   222 	$hsl_regexp = '/^hsl' . $permissive_match3 . '$/';
   247 	$hsl_regexp = '/^hsl' . $permissive_match3 . '$/';
   223 	if ( preg_match( $hsl_regexp, $color_str, $match ) ) {
   248 	if ( preg_match( $hsl_regexp, $color_str, $match ) ) {
   224 		return wp_tinycolor_hsl_to_rgb(
   249 		$rgb = wp_tinycolor_hsl_to_rgb(
   225 			array(
   250 			array(
   226 				'h' => $match[1],
   251 				'h' => $match[1],
   227 				's' => $match[2],
   252 				's' => $match[2],
   228 				'l' => $match[3],
   253 				'l' => $match[3],
   229 			)
   254 			)
   230 		);
   255 		);
       
   256 
       
   257 		$rgb['a'] = 1;
       
   258 
       
   259 		return $rgb;
   231 	}
   260 	}
   232 
   261 
   233 	$hsla_regexp = '/^hsla' . $permissive_match4 . '$/';
   262 	$hsla_regexp = '/^hsla' . $permissive_match4 . '$/';
   234 	if ( preg_match( $hsla_regexp, $color_str, $match ) ) {
   263 	if ( preg_match( $hsla_regexp, $color_str, $match ) ) {
   235 		return wp_tinycolor_hsl_to_rgb(
   264 		$rgb = wp_tinycolor_hsl_to_rgb(
   236 			array(
   265 			array(
   237 				'h' => $match[1],
   266 				'h' => $match[1],
   238 				's' => $match[2],
   267 				's' => $match[2],
   239 				'l' => $match[3],
   268 				'l' => $match[3],
   240 			)
   269 			)
   241 		);
   270 		);
       
   271 
       
   272 		$rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );
       
   273 
       
   274 		return $rgb;
   242 	}
   275 	}
   243 
   276 
   244 	$hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
   277 	$hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
   245 	if ( preg_match( $hex8_regexp, $color_str, $match ) ) {
   278 	if ( preg_match( $hex8_regexp, $color_str, $match ) ) {
   246 		return wp_tinycolor_rgb_to_rgb(
   279 		$rgb = wp_tinycolor_rgb_to_rgb(
   247 			array(
   280 			array(
   248 				'r' => base_convert( $match[1], 16, 10 ),
   281 				'r' => base_convert( $match[1], 16, 10 ),
   249 				'g' => base_convert( $match[2], 16, 10 ),
   282 				'g' => base_convert( $match[2], 16, 10 ),
   250 				'b' => base_convert( $match[3], 16, 10 ),
   283 				'b' => base_convert( $match[3], 16, 10 ),
   251 			)
   284 			)
   252 		);
   285 		);
       
   286 
       
   287 		$rgb['a'] = _wp_tinycolor_bound_alpha(
       
   288 			base_convert( $match[4], 16, 10 ) / 255
       
   289 		);
       
   290 
       
   291 		return $rgb;
   253 	}
   292 	}
   254 
   293 
   255 	$hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
   294 	$hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
   256 	if ( preg_match( $hex6_regexp, $color_str, $match ) ) {
   295 	if ( preg_match( $hex6_regexp, $color_str, $match ) ) {
   257 		return wp_tinycolor_rgb_to_rgb(
   296 		$rgb = wp_tinycolor_rgb_to_rgb(
   258 			array(
   297 			array(
   259 				'r' => base_convert( $match[1], 16, 10 ),
   298 				'r' => base_convert( $match[1], 16, 10 ),
   260 				'g' => base_convert( $match[2], 16, 10 ),
   299 				'g' => base_convert( $match[2], 16, 10 ),
   261 				'b' => base_convert( $match[3], 16, 10 ),
   300 				'b' => base_convert( $match[3], 16, 10 ),
   262 			)
   301 			)
   263 		);
   302 		);
       
   303 
       
   304 		$rgb['a'] = 1;
       
   305 
       
   306 		return $rgb;
   264 	}
   307 	}
   265 
   308 
   266 	$hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
   309 	$hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
   267 	if ( preg_match( $hex4_regexp, $color_str, $match ) ) {
   310 	if ( preg_match( $hex4_regexp, $color_str, $match ) ) {
   268 		return wp_tinycolor_rgb_to_rgb(
   311 		$rgb = wp_tinycolor_rgb_to_rgb(
   269 			array(
   312 			array(
   270 				'r' => base_convert( $match[1] . $match[1], 16, 10 ),
   313 				'r' => base_convert( $match[1] . $match[1], 16, 10 ),
   271 				'g' => base_convert( $match[2] . $match[2], 16, 10 ),
   314 				'g' => base_convert( $match[2] . $match[2], 16, 10 ),
   272 				'b' => base_convert( $match[3] . $match[3], 16, 10 ),
   315 				'b' => base_convert( $match[3] . $match[3], 16, 10 ),
   273 			)
   316 			)
   274 		);
   317 		);
       
   318 
       
   319 		$rgb['a'] = _wp_tinycolor_bound_alpha(
       
   320 			base_convert( $match[4] . $match[4], 16, 10 ) / 255
       
   321 		);
       
   322 
       
   323 		return $rgb;
   275 	}
   324 	}
   276 
   325 
   277 	$hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
   326 	$hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
   278 	if ( preg_match( $hex3_regexp, $color_str, $match ) ) {
   327 	if ( preg_match( $hex3_regexp, $color_str, $match ) ) {
   279 		return wp_tinycolor_rgb_to_rgb(
   328 		$rgb = wp_tinycolor_rgb_to_rgb(
   280 			array(
   329 			array(
   281 				'r' => base_convert( $match[1] . $match[1], 16, 10 ),
   330 				'r' => base_convert( $match[1] . $match[1], 16, 10 ),
   282 				'g' => base_convert( $match[2] . $match[2], 16, 10 ),
   331 				'g' => base_convert( $match[2] . $match[2], 16, 10 ),
   283 				'b' => base_convert( $match[3] . $match[3], 16, 10 ),
   332 				'b' => base_convert( $match[3] . $match[3], 16, 10 ),
   284 			)
   333 			)
   285 		);
   334 		);
   286 	}
   335 
   287 }
   336 		$rgb['a'] = 1;
   288 
   337 
   289 
   338 		return $rgb;
   290 /**
   339 	}
   291  * Registers the style and colors block attributes for block types that support it.
   340 
   292  *
   341 	/*
   293  * @since 5.8.0
   342 	 * The JS color picker considers the string "transparent" to be a hex value,
   294  * @access private
   343 	 * so we need to handle it here as a special case.
   295  *
   344 	 */
   296  * @param WP_Block_Type $block_type Block Type.
   345 	if ( 'transparent' === $color_str ) {
   297  */
   346 		return array(
   298 function wp_register_duotone_support( $block_type ) {
   347 			'r' => 0,
   299 	$has_duotone_support = false;
   348 			'g' => 0,
   300 	if ( property_exists( $block_type, 'supports' ) ) {
   349 			'b' => 0,
   301 		$has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
   350 			'a' => 0,
   302 	}
   351 		);
   303 
   352 	}
   304 	if ( $has_duotone_support ) {
   353 }
   305 		if ( ! $block_type->attributes ) {
   354 
   306 			$block_type->attributes = array();
   355 /**
   307 		}
   356  * Returns the prefixed id for the duotone filter for use as a CSS id.
   308 
   357  *
   309 		if ( ! array_key_exists( 'style', $block_type->attributes ) ) {
   358  * @since 5.9.1
   310 			$block_type->attributes['style'] = array(
   359  * @access private
   311 				'type' => 'object',
   360  *
   312 			);
   361  * @param array $preset Duotone preset value as seen in theme.json.
   313 		}
   362  * @return string Duotone filter CSS id.
   314 	}
   363  */
   315 }
   364 function wp_get_duotone_filter_id( $preset ) {
   316 
   365 	if ( ! isset( $preset['slug'] ) ) {
   317 /**
   366 		return '';
   318  * Render out the duotone stylesheet and SVG.
   367 	}
   319  *
   368 
   320  * @since 5.8.0
   369 	return 'wp-duotone-' . $preset['slug'];
   321  * @access private
   370 }
   322  *
   371 
   323  * @param string $block_content Rendered block content.
   372 /**
   324  * @param array  $block         Block object.
   373  * Returns the CSS filter property url to reference the rendered SVG.
   325  *
   374  *
   326  * @return string Filtered block content.
   375  * @since 5.9.0
   327  */
   376  * @access private
   328 function wp_render_duotone_support( $block_content, $block ) {
   377  *
   329 	$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
   378  * @param array $preset Duotone preset value as seen in theme.json.
   330 
   379  * @return string Duotone CSS filter property url value.
   331 	$duotone_support = false;
   380  */
   332 	if ( $block_type && property_exists( $block_type, 'supports' ) ) {
   381 function wp_get_duotone_filter_property( $preset ) {
   333 		$duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
   382 	$filter_id = wp_get_duotone_filter_id( $preset );
   334 	}
   383 	return "url('#" . $filter_id . "')";
   335 
   384 }
   336 	$has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
   385 
   337 
   386 /**
   338 	if (
   387  * Returns the duotone filter SVG string for the preset.
   339 		! $duotone_support ||
   388  *
   340 		! $has_duotone_attribute
   389  * @since 5.9.1
   341 	) {
   390  * @access private
   342 		return $block_content;
   391  *
   343 	}
   392  * @param array $preset Duotone preset value as seen in theme.json.
   344 
   393  * @return string Duotone SVG filter.
   345 	$duotone_colors = $block['attrs']['style']['color']['duotone'];
   394  */
       
   395 function wp_get_duotone_filter_svg( $preset ) {
       
   396 	$filter_id = wp_get_duotone_filter_id( $preset );
   346 
   397 
   347 	$duotone_values = array(
   398 	$duotone_values = array(
   348 		'r' => array(),
   399 		'r' => array(),
   349 		'g' => array(),
   400 		'g' => array(),
   350 		'b' => array(),
   401 		'b' => array(),
       
   402 		'a' => array(),
   351 	);
   403 	);
   352 	foreach ( $duotone_colors as $color_str ) {
   404 
       
   405 	if ( ! isset( $preset['colors'] ) || ! is_array( $preset['colors'] ) ) {
       
   406 		$preset['colors'] = array();
       
   407 	}
       
   408 
       
   409 	foreach ( $preset['colors'] as $color_str ) {
   353 		$color = wp_tinycolor_string_to_rgb( $color_str );
   410 		$color = wp_tinycolor_string_to_rgb( $color_str );
   354 
   411 
   355 		$duotone_values['r'][] = $color['r'] / 255;
   412 		$duotone_values['r'][] = $color['r'] / 255;
   356 		$duotone_values['g'][] = $color['g'] / 255;
   413 		$duotone_values['g'][] = $color['g'] / 255;
   357 		$duotone_values['b'][] = $color['b'] / 255;
   414 		$duotone_values['b'][] = $color['b'] / 255;
   358 	}
   415 		$duotone_values['a'][] = $color['a'];
   359 
   416 	}
   360 	$duotone_id = 'wp-duotone-filter-' . uniqid();
       
   361 
       
   362 	$selectors        = explode( ',', $duotone_support );
       
   363 	$selectors_scoped = array_map(
       
   364 		function ( $selector ) use ( $duotone_id ) {
       
   365 			return '.' . $duotone_id . ' ' . trim( $selector );
       
   366 		},
       
   367 		$selectors
       
   368 	);
       
   369 	$selectors_group  = implode( ', ', $selectors_scoped );
       
   370 
   417 
   371 	ob_start();
   418 	ob_start();
   372 
   419 
   373 	?>
   420 	?>
   374 
   421 
   375 	<style>
       
   376 		<?php echo $selectors_group; ?> {
       
   377 			filter: url( <?php echo esc_url( '#' . $duotone_id ); ?> );
       
   378 		}
       
   379 	</style>
       
   380 
       
   381 	<svg
   422 	<svg
   382 		xmlns:xlink="http://www.w3.org/1999/xlink"
   423 		xmlns="http://www.w3.org/2000/svg"
   383 		viewBox="0 0 0 0"
   424 		viewBox="0 0 0 0"
   384 		width="0"
   425 		width="0"
   385 		height="0"
   426 		height="0"
   386 		focusable="false"
   427 		focusable="false"
   387 		role="none"
   428 		role="none"
   388 		style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
   429 		style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
   389 	>
   430 	>
   390 		<defs>
   431 		<defs>
   391 			<filter id="<?php echo esc_attr( $duotone_id ); ?>">
   432 			<filter id="<?php echo esc_attr( $filter_id ); ?>">
   392 				<feColorMatrix
   433 				<feColorMatrix
       
   434 					color-interpolation-filters="sRGB"
   393 					type="matrix"
   435 					type="matrix"
   394 					<?php // phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent ?>
   436 					values="
   395 					values=".299 .587 .114 0 0
   437 						.299 .587 .114 0 0
   396 							.299 .587 .114 0 0
   438 						.299 .587 .114 0 0
   397 							.299 .587 .114 0 0
   439 						.299 .587 .114 0 0
   398 							0 0 0 1 0"
   440 						.299 .587 .114 0 0
   399 					<?php // phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent ?>
   441 					"
   400 				/>
   442 				/>
   401 				<feComponentTransfer color-interpolation-filters="sRGB" >
   443 				<feComponentTransfer color-interpolation-filters="sRGB" >
   402 					<feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
   444 					<feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
   403 					<feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
   445 					<feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
   404 					<feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />
   446 					<feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />
       
   447 					<feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" />
   405 				</feComponentTransfer>
   448 				</feComponentTransfer>
       
   449 				<feComposite in2="SourceGraphic" operator="in" />
   406 			</filter>
   450 			</filter>
   407 		</defs>
   451 		</defs>
   408 	</svg>
   452 	</svg>
   409 
   453 
   410 	<?php
   454 	<?php
   411 
   455 
   412 	$duotone = ob_get_clean();
   456 	$svg = ob_get_clean();
       
   457 
       
   458 	if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) {
       
   459 		// Clean up the whitespace.
       
   460 		$svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg );
       
   461 		$svg = preg_replace( '/> </', '><', $svg );
       
   462 		$svg = trim( $svg );
       
   463 	}
       
   464 
       
   465 	return $svg;
       
   466 }
       
   467 
       
   468 /**
       
   469  * Registers the style and colors block attributes for block types that support it.
       
   470  *
       
   471  * @since 5.8.0
       
   472  * @access private
       
   473  *
       
   474  * @param WP_Block_Type $block_type Block Type.
       
   475  */
       
   476 function wp_register_duotone_support( $block_type ) {
       
   477 	$has_duotone_support = false;
       
   478 	if ( property_exists( $block_type, 'supports' ) ) {
       
   479 		$has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
       
   480 	}
       
   481 
       
   482 	if ( $has_duotone_support ) {
       
   483 		if ( ! $block_type->attributes ) {
       
   484 			$block_type->attributes = array();
       
   485 		}
       
   486 
       
   487 		if ( ! array_key_exists( 'style', $block_type->attributes ) ) {
       
   488 			$block_type->attributes['style'] = array(
       
   489 				'type' => 'object',
       
   490 			);
       
   491 		}
       
   492 	}
       
   493 }
       
   494 
       
   495 /**
       
   496  * Render out the duotone stylesheet and SVG.
       
   497  *
       
   498  * @since 5.8.0
       
   499  * @access private
       
   500  *
       
   501  * @param string $block_content Rendered block content.
       
   502  * @param array  $block         Block object.
       
   503  * @return string Filtered block content.
       
   504  */
       
   505 function wp_render_duotone_support( $block_content, $block ) {
       
   506 	$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
       
   507 
       
   508 	$duotone_support = false;
       
   509 	if ( $block_type && property_exists( $block_type, 'supports' ) ) {
       
   510 		$duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
       
   511 	}
       
   512 
       
   513 	$has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
       
   514 
       
   515 	if (
       
   516 		! $duotone_support ||
       
   517 		! $has_duotone_attribute
       
   518 	) {
       
   519 		return $block_content;
       
   520 	}
       
   521 
       
   522 	$filter_preset   = array(
       
   523 		'slug'   => wp_unique_id( sanitize_key( implode( '-', $block['attrs']['style']['color']['duotone'] ) . '-' ) ),
       
   524 		'colors' => $block['attrs']['style']['color']['duotone'],
       
   525 	);
       
   526 	$filter_property = wp_get_duotone_filter_property( $filter_preset );
       
   527 	$filter_id       = wp_get_duotone_filter_id( $filter_preset );
       
   528 	$filter_svg      = wp_get_duotone_filter_svg( $filter_preset );
       
   529 
       
   530 	$scope     = '.' . $filter_id;
       
   531 	$selectors = explode( ',', $duotone_support );
       
   532 	$scoped    = array();
       
   533 	foreach ( $selectors as $sel ) {
       
   534 		$scoped[] = $scope . ' ' . trim( $sel );
       
   535 	}
       
   536 	$selector = implode( ', ', $scoped );
       
   537 
       
   538 	// !important is needed because these styles render before global styles,
       
   539 	// and they should be overriding the duotone filters set by global styles.
       
   540 	$filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG
       
   541 		? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n"
       
   542 		: $selector . '{filter:' . $filter_property . ' !important;}';
       
   543 
       
   544 	wp_register_style( $filter_id, false, array(), true, true );
       
   545 	wp_add_inline_style( $filter_id, $filter_style );
       
   546 	wp_enqueue_style( $filter_id );
       
   547 
       
   548 	add_action(
       
   549 		'wp_footer',
       
   550 		static function () use ( $filter_svg, $selector ) {
       
   551 			echo $filter_svg;
       
   552 
       
   553 			/*
       
   554 			 * Safari renders elements incorrectly on first paint when the SVG
       
   555 			 * filter comes after the content that it is filtering, so we force
       
   556 			 * a repaint with a WebKit hack which solves the issue.
       
   557 			 */
       
   558 			global $is_safari;
       
   559 			if ( $is_safari ) {
       
   560 				printf(
       
   561 					// Simply accessing el.offsetHeight flushes layout and style
       
   562 					// changes in WebKit without having to wait for setTimeout.
       
   563 					'<script>( function() { var el = document.querySelector( %s ); var display = el.style.display; el.style.display = "none"; el.offsetHeight; el.style.display = display; } )();</script>',
       
   564 					wp_json_encode( $selector )
       
   565 				);
       
   566 			}
       
   567 		}
       
   568 	);
   413 
   569 
   414 	// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
   570 	// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
   415 	$content = preg_replace(
   571 	return preg_replace(
   416 		'/' . preg_quote( 'class="', '/' ) . '/',
   572 		'/' . preg_quote( 'class="', '/' ) . '/',
   417 		'class="' . $duotone_id . ' ',
   573 		'class="' . $filter_id . ' ',
   418 		$block_content,
   574 		$block_content,
   419 		1
   575 		1
   420 	);
   576 	);
   421 
       
   422 	return $content . $duotone;
       
   423 }
   577 }
   424 
   578 
   425 // Register the block support.
   579 // Register the block support.
   426 WP_Block_Supports::get_instance()->register(
   580 WP_Block_Supports::get_instance()->register(
   427 	'duotone',
   581 	'duotone',