Changeset 56101 for trunk/src/wp-includes/block-supports/duotone.php
- Timestamp:
- 06/29/2023 06:19:41 AM (18 months ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/block-supports/duotone.php
r55988 r56101 33 33 */ 34 34 35 /**36 * Takes input from [0, n] and returns it as [0, 1].37 *38 * Direct port of TinyColor's function, lightly simplified to maintain39 * consistency with TinyColor.40 *41 * @see https://github.com/bgrins/TinyColor42 *43 * @since 5.8.044 * @access private45 *46 * @param mixed $n Number of unknown type.47 * @param int $max Upper value of the range to bound to.48 * @return float Value in the range [0, 1].49 */50 function wp_tinycolor_bound01( $n, $max ) {51 if ( 'string' === gettype( $n ) && str_contains( $n, '.' ) && 1 === (float) $n ) {52 $n = '100%';53 }54 55 $n = min( $max, max( 0, (float) $n ) );56 57 // Automatically convert percentage into number.58 if ( 'string' === gettype( $n ) && str_contains( $n, '%' ) ) {59 $n = (int) ( $n * $max ) / 100;60 }61 62 // Handle floating point rounding errors.63 if ( ( abs( $n - $max ) < 0.000001 ) ) {64 return 1.0;65 }66 67 // Convert into [0, 1] range if it isn't already.68 return ( $n % $max ) / (float) $max;69 }70 71 /**72 * Direct port of tinycolor's boundAlpha function to maintain consistency with73 * how tinycolor works.74 *75 * @see https://github.com/bgrins/TinyColor76 *77 * @since 5.9.078 * @access private79 *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.95 *96 * Direct port of TinyColor's function, lightly simplified to maintain97 * consistency with TinyColor.98 *99 * @see https://github.com/bgrins/TinyColor100 *101 * @since 5.8.0102 * @access private103 *104 * @param array $rgb_color RGB object.105 * @return array Rounded and converted RGB object.106 */107 function wp_tinycolor_rgb_to_rgb( $rgb_color ) {108 return array(109 'r' => wp_tinycolor_bound01( $rgb_color['r'], 255 ) * 255,110 'g' => wp_tinycolor_bound01( $rgb_color['g'], 255 ) * 255,111 'b' => wp_tinycolor_bound01( $rgb_color['b'], 255 ) * 255,112 );113 }114 115 /**116 * Helper function for hsl to rgb conversion.117 *118 * Direct port of TinyColor's function, lightly simplified to maintain119 * consistency with TinyColor.120 *121 * @see https://github.com/bgrins/TinyColor122 *123 * @since 5.8.0124 * @access private125 *126 * @param float $p first component.127 * @param float $q second component.128 * @param float $t third component.129 * @return float R, G, or B component.130 */131 function wp_tinycolor_hue_to_rgb( $p, $q, $t ) {132 if ( $t < 0 ) {133 ++$t;134 }135 if ( $t > 1 ) {136 --$t;137 }138 if ( $t < 1 / 6 ) {139 return $p + ( $q - $p ) * 6 * $t;140 }141 if ( $t < 1 / 2 ) {142 return $q;143 }144 if ( $t < 2 / 3 ) {145 return $p + ( $q - $p ) * ( 2 / 3 - $t ) * 6;146 }147 return $p;148 }149 150 /**151 * Converts an HSL object to an RGB object with converted and rounded values.152 *153 * Direct port of TinyColor's function, lightly simplified to maintain154 * consistency with TinyColor.155 *156 * @see https://github.com/bgrins/TinyColor157 *158 * @since 5.8.0159 * @access private160 *161 * @param array $hsl_color HSL object.162 * @return array Rounded and converted RGB object.163 */164 function wp_tinycolor_hsl_to_rgb( $hsl_color ) {165 $h = wp_tinycolor_bound01( $hsl_color['h'], 360 );166 $s = wp_tinycolor_bound01( $hsl_color['s'], 100 );167 $l = wp_tinycolor_bound01( $hsl_color['l'], 100 );168 169 if ( 0 === $s ) {170 // Achromatic.171 $r = $l;172 $g = $l;173 $b = $l;174 } else {175 $q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s;176 $p = 2 * $l - $q;177 $r = wp_tinycolor_hue_to_rgb( $p, $q, $h + 1 / 3 );178 $g = wp_tinycolor_hue_to_rgb( $p, $q, $h );179 $b = wp_tinycolor_hue_to_rgb( $p, $q, $h - 1 / 3 );180 }181 182 return array(183 'r' => $r * 255,184 'g' => $g * 255,185 'b' => $b * 255,186 );187 }188 189 /**190 * Parses hex, hsl, and rgb CSS strings using the same regex as TinyColor v1.4.2191 * used in the JavaScript. Only colors output from react-color are implemented.192 *193 * Direct port of TinyColor's function, lightly simplified to maintain194 * consistency with TinyColor.195 *196 * @see https://github.com/bgrins/TinyColor197 * @see https://github.com/casesandberg/react-color/198 *199 * @since 5.8.0200 * @since 5.9.0 Added alpha processing.201 * @access private202 *203 * @param string $color_str CSS color string.204 * @return array RGB object.205 */206 function wp_tinycolor_string_to_rgb( $color_str ) {207 $color_str = strtolower( trim( $color_str ) );208 209 $css_integer = '[-\\+]?\\d+%?';210 $css_number = '[-\\+]?\\d*\\.\\d+%?';211 212 $css_unit = '(?:' . $css_number . ')|(?:' . $css_integer . ')';213 214 $permissive_match3 = '[\\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*\\)?';216 217 $rgb_regexp = '/^rgb' . $permissive_match3 . '$/';218 if ( preg_match( $rgb_regexp, $color_str, $match ) ) {219 $rgb = wp_tinycolor_rgb_to_rgb(220 array(221 'r' => $match[1],222 'g' => $match[2],223 'b' => $match[3],224 )225 );226 227 $rgb['a'] = 1;228 229 return $rgb;230 }231 232 $rgba_regexp = '/^rgba' . $permissive_match4 . '$/';233 if ( preg_match( $rgba_regexp, $color_str, $match ) ) {234 $rgb = wp_tinycolor_rgb_to_rgb(235 array(236 'r' => $match[1],237 'g' => $match[2],238 'b' => $match[3],239 )240 );241 242 $rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );243 244 return $rgb;245 }246 247 $hsl_regexp = '/^hsl' . $permissive_match3 . '$/';248 if ( preg_match( $hsl_regexp, $color_str, $match ) ) {249 $rgb = wp_tinycolor_hsl_to_rgb(250 array(251 'h' => $match[1],252 's' => $match[2],253 'l' => $match[3],254 )255 );256 257 $rgb['a'] = 1;258 259 return $rgb;260 }261 262 $hsla_regexp = '/^hsla' . $permissive_match4 . '$/';263 if ( preg_match( $hsla_regexp, $color_str, $match ) ) {264 $rgb = wp_tinycolor_hsl_to_rgb(265 array(266 'h' => $match[1],267 's' => $match[2],268 'l' => $match[3],269 )270 );271 272 $rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );273 274 return $rgb;275 }276 277 $hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';278 if ( preg_match( $hex8_regexp, $color_str, $match ) ) {279 $rgb = wp_tinycolor_rgb_to_rgb(280 array(281 'r' => base_convert( $match[1], 16, 10 ),282 'g' => base_convert( $match[2], 16, 10 ),283 'b' => base_convert( $match[3], 16, 10 ),284 )285 );286 287 $rgb['a'] = _wp_tinycolor_bound_alpha(288 base_convert( $match[4], 16, 10 ) / 255289 );290 291 return $rgb;292 }293 294 $hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';295 if ( preg_match( $hex6_regexp, $color_str, $match ) ) {296 $rgb = wp_tinycolor_rgb_to_rgb(297 array(298 'r' => base_convert( $match[1], 16, 10 ),299 'g' => base_convert( $match[2], 16, 10 ),300 'b' => base_convert( $match[3], 16, 10 ),301 )302 );303 304 $rgb['a'] = 1;305 306 return $rgb;307 }308 309 $hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';310 if ( preg_match( $hex4_regexp, $color_str, $match ) ) {311 $rgb = wp_tinycolor_rgb_to_rgb(312 array(313 'r' => base_convert( $match[1] . $match[1], 16, 10 ),314 'g' => base_convert( $match[2] . $match[2], 16, 10 ),315 'b' => base_convert( $match[3] . $match[3], 16, 10 ),316 )317 );318 319 $rgb['a'] = _wp_tinycolor_bound_alpha(320 base_convert( $match[4] . $match[4], 16, 10 ) / 255321 );322 323 return $rgb;324 }325 326 $hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';327 if ( preg_match( $hex3_regexp, $color_str, $match ) ) {328 $rgb = wp_tinycolor_rgb_to_rgb(329 array(330 'r' => base_convert( $match[1] . $match[1], 16, 10 ),331 'g' => base_convert( $match[2] . $match[2], 16, 10 ),332 'b' => base_convert( $match[3] . $match[3], 16, 10 ),333 )334 );335 336 $rgb['a'] = 1;337 338 return $rgb;339 }340 341 /*342 * The JS color picker considers the string "transparent" to be a hex value,343 * so we need to handle it here as a special case.344 */345 if ( 'transparent' === $color_str ) {346 return array(347 'r' => 0,348 'g' => 0,349 'b' => 0,350 'a' => 0,351 );352 }353 }354 355 /**356 * Returns the prefixed id for the duotone filter for use as a CSS id.357 *358 * @since 5.9.1359 * @access private360 *361 * @param array $preset Duotone preset value as seen in theme.json.362 * @return string Duotone filter CSS id.363 */364 function wp_get_duotone_filter_id( $preset ) {365 if ( ! isset( $preset['slug'] ) ) {366 return '';367 }368 369 return 'wp-duotone-' . $preset['slug'];370 }371 372 /**373 * Returns the CSS filter property url to reference the rendered SVG.374 *375 * @since 5.9.0376 * @since 6.1.0 Allow unset for preset colors.377 * @access private378 *379 * @param array $preset Duotone preset value as seen in theme.json.380 * @return string Duotone CSS filter property url value.381 */382 function wp_get_duotone_filter_property( $preset ) {383 if ( isset( $preset['colors'] ) && 'unset' === $preset['colors'] ) {384 return 'none';385 }386 $filter_id = wp_get_duotone_filter_id( $preset );387 return "url('#" . $filter_id . "')";388 }389 390 /**391 * Returns the duotone filter SVG string for the preset.392 *393 * @since 5.9.1394 * @access private395 *396 * @param array $preset Duotone preset value as seen in theme.json.397 * @return string Duotone SVG filter.398 */399 function wp_get_duotone_filter_svg( $preset ) {400 $filter_id = wp_get_duotone_filter_id( $preset );401 402 $duotone_values = array(403 'r' => array(),404 'g' => array(),405 'b' => array(),406 'a' => array(),407 );408 409 if ( ! isset( $preset['colors'] ) || ! is_array( $preset['colors'] ) ) {410 $preset['colors'] = array();411 }412 413 foreach ( $preset['colors'] as $color_str ) {414 $color = wp_tinycolor_string_to_rgb( $color_str );415 416 $duotone_values['r'][] = $color['r'] / 255;417 $duotone_values['g'][] = $color['g'] / 255;418 $duotone_values['b'][] = $color['b'] / 255;419 $duotone_values['a'][] = $color['a'];420 }421 422 ob_start();423 424 ?>425 426 <svg427 xmlns="http://www.w3.org/2000/svg"428 viewBox="0 0 0 0"429 width="0"430 height="0"431 focusable="false"432 role="none"433 style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"434 >435 <defs>436 <filter id="<?php echo esc_attr( $filter_id ); ?>">437 <feColorMatrix438 color-interpolation-filters="sRGB"439 type="matrix"440 values="441 .299 .587 .114 0 0442 .299 .587 .114 0 0443 .299 .587 .114 0 0444 .299 .587 .114 0 0445 "446 />447 <feComponentTransfer color-interpolation-filters="sRGB" >448 <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />449 <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />450 <feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />451 <feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" />452 </feComponentTransfer>453 <feComposite in2="SourceGraphic" operator="in" />454 </filter>455 </defs>456 </svg>457 458 <?php459 460 $svg = ob_get_clean();461 462 if ( ! SCRIPT_DEBUG ) {463 // Clean up the whitespace.464 $svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg );465 $svg = str_replace( '> <', '><', $svg );466 $svg = trim( $svg );467 }468 469 return $svg;470 }471 472 /**473 * Registers the style and colors block attributes for block types that support it.474 *475 * @since 5.8.0476 * @access private477 *478 * @param WP_Block_Type $block_type Block Type.479 */480 function wp_register_duotone_support( $block_type ) {481 $has_duotone_support = false;482 if ( property_exists( $block_type, 'supports' ) ) {483 $has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );484 }485 486 if ( $has_duotone_support ) {487 if ( ! $block_type->attributes ) {488 $block_type->attributes = array();489 }490 491 if ( ! array_key_exists( 'style', $block_type->attributes ) ) {492 $block_type->attributes['style'] = array(493 'type' => 'object',494 );495 }496 }497 }498 499 /**500 * Renders out the duotone stylesheet and SVG.501 *502 * @since 5.8.0503 * @since 6.1.0 Allow unset for preset colors.504 * @access private505 *506 * @param string $block_content Rendered block content.507 * @param array $block Block object.508 * @return string Filtered block content.509 */510 function wp_render_duotone_support( $block_content, $block ) {511 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );512 513 $duotone_support = false;514 if ( $block_type && property_exists( $block_type, 'supports' ) ) {515 $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );516 }517 518 $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );519 520 if (521 ! $duotone_support ||522 ! $has_duotone_attribute523 ) {524 return $block_content;525 }526 527 $colors = $block['attrs']['style']['color']['duotone'];528 $filter_key = is_array( $colors ) ? implode( '-', $colors ) : $colors;529 $filter_preset = array(530 'slug' => wp_unique_id( sanitize_key( $filter_key . '-' ) ),531 'colors' => $colors,532 );533 $filter_property = wp_get_duotone_filter_property( $filter_preset );534 $filter_id = wp_get_duotone_filter_id( $filter_preset );535 536 $scope = '.' . $filter_id;537 $selectors = explode( ',', $duotone_support );538 $scoped = array();539 foreach ( $selectors as $sel ) {540 $scoped[] = $scope . ' ' . trim( $sel );541 }542 $selector = implode( ', ', $scoped );543 544 // !important is needed because these styles render before global styles,545 // and they should be overriding the duotone filters set by global styles.546 $filter_style = SCRIPT_DEBUG547 ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n"548 : $selector . '{filter:' . $filter_property . ' !important;}';549 550 wp_register_style( $filter_id, false );551 wp_add_inline_style( $filter_id, $filter_style );552 wp_enqueue_style( $filter_id );553 554 if ( 'unset' !== $colors ) {555 $filter_svg = wp_get_duotone_filter_svg( $filter_preset );556 add_action(557 'wp_footer',558 static function () use ( $filter_svg, $selector ) {559 echo $filter_svg;560 561 /*562 * Safari renders elements incorrectly on first paint when the563 * SVG filter comes after the content that it is filtering, so564 * we force a repaint with a WebKit hack which solves the issue.565 */566 global $is_safari;567 if ( $is_safari ) {568 /*569 * Simply accessing el.offsetHeight flushes layout and style570 * changes in WebKit without having to wait for setTimeout.571 */572 printf(573 '<script>( function() { var el = document.querySelector( %s ); var display = el.style.display; el.style.display = "none"; el.offsetHeight; el.style.display = display; } )();</script>',574 wp_json_encode( $selector )575 );576 }577 }578 );579 }580 581 // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.582 return preg_replace(583 '/' . preg_quote( 'class="', '/' ) . '/',584 'class="' . $filter_id . ' ',585 $block_content,586 1587 );588 }589 590 35 // Register the block support. 591 36 WP_Block_Supports::get_instance()->register( 592 37 'duotone', 593 38 array( 594 'register_attribute' => 'wp_register_duotone_support',39 'register_attribute' => array( 'WP_Duotone', 'register_duotone_support' ), 595 40 ) 596 41 ); 597 add_filter( 'render_block', 'wp_render_duotone_support', 10, 2 ); 42 43 // Set up metadata prior to rendering any blocks. 44 add_action( 'wp_loaded', array( 'WP_Duotone', 'set_global_styles_presets' ), 10 ); 45 add_action( 'wp_loaded', array( 'WP_Duotone', 'set_global_style_block_names' ), 10 ); 46 47 // Add classnames to blocks using duotone support. 48 add_filter( 'render_block', array( 'WP_Duotone', 'render_duotone_support' ), 10, 2 ); 49 50 // Enqueue styles. 51 // Block styles (core-block-supports-inline-css) before the style engine (wp_enqueue_stored_styles). 52 // Global styles (global-styles-inline-css) after the other global styles (wp_enqueue_global_styles). 53 add_action( 'wp_enqueue_scripts', array( 'WP_Duotone', 'output_block_styles' ), 9 ); 54 add_action( 'wp_enqueue_scripts', array( 'WP_Duotone', 'output_global_styles' ), 11 ); 55 56 // Add SVG filters to the footer. Also, for classic themes, output block styles (core-block-supports-inline-css). 57 add_action( 'wp_footer', array( 'WP_Duotone', 'output_footer_assets' ), 10 ); 58 59 // Add styles and SVGs for use in the editor via the EditorStyles component. 60 add_filter( 'block_editor_settings_all', array( 'WP_Duotone', 'add_editor_settings' ), 10 ); 61 62 // Migrate the old experimental duotone support flag. 63 add_filter( 'block_type_metadata_settings', array( 'WP_Duotone', 'migrate_experimental_duotone_support_flag' ), 10, 2 );
Note: See TracChangeset
for help on using the changeset viewer.