--- a/wp/wp-includes/block-supports/duotone.php Wed Sep 21 18:19:35 2022 +0200
+++ b/wp/wp-includes/block-supports/duotone.php Tue Sep 27 16:37:53 2022 +0200
@@ -45,7 +45,6 @@
*
* @param mixed $n Number of unknown type.
* @param int $max Upper value of the range to bound to.
- *
* @return float Value in the range [0, 1].
*/
function wp_tinycolor_bound01( $n, $max ) {
@@ -70,7 +69,29 @@
}
/**
- * Round and convert values of an RGB object.
+ * Direct port of tinycolor's boundAlpha function to maintain consistency with
+ * how tinycolor works.
+ *
+ * @see https://github.com/bgrins/TinyColor
+ *
+ * @since 5.9.0
+ * @access private
+ *
+ * @param mixed $n Number of unknown type.
+ * @return float Value in the range [0,1].
+ */
+function _wp_tinycolor_bound_alpha( $n ) {
+ if ( is_numeric( $n ) ) {
+ $n = (float) $n;
+ if ( $n >= 0 && $n <= 1 ) {
+ return $n;
+ }
+ }
+ return 1;
+}
+
+/**
+ * Rounds and converts values of an RGB object.
*
* Direct port of TinyColor's function, lightly simplified to maintain
* consistency with TinyColor.
@@ -81,7 +102,6 @@
* @access private
*
* @param array $rgb_color RGB object.
- *
* @return array Rounded and converted RGB object.
*/
function wp_tinycolor_rgb_to_rgb( $rgb_color ) {
@@ -106,7 +126,6 @@
* @param float $p first component.
* @param float $q second component.
* @param float $t third component.
- *
* @return float R, G, or B component.
*/
function wp_tinycolor_hue_to_rgb( $p, $q, $t ) {
@@ -129,7 +148,7 @@
}
/**
- * Convert an HSL object to an RGB object with converted and rounded values.
+ * Converts an HSL object to an RGB object with converted and rounded values.
*
* Direct port of TinyColor's function, lightly simplified to maintain
* consistency with TinyColor.
@@ -140,7 +159,6 @@
* @access private
*
* @param array $hsl_color HSL object.
- *
* @return array Rounded and converted RGB object.
*/
function wp_tinycolor_hsl_to_rgb( $hsl_color ) {
@@ -170,8 +188,7 @@
/**
* Parses hex, hsl, and rgb CSS strings using the same regex as TinyColor v1.4.2
- * used in the JavaScript. Only colors output from react-color are implemented
- * and the alpha value is ignored as it is not used in duotone.
+ * used in the JavaScript. Only colors output from react-color are implemented.
*
* Direct port of TinyColor's function, lightly simplified to maintain
* consistency with TinyColor.
@@ -180,10 +197,10 @@
* @see https://github.com/casesandberg/react-color/
*
* @since 5.8.0
+ * @since 5.9.0 Added alpha processing.
* @access private
*
* @param string $color_str CSS color string.
- *
* @return array RGB object.
*/
function wp_tinycolor_string_to_rgb( $color_str ) {
@@ -199,93 +216,254 @@
$rgb_regexp = '/^rgb' . $permissive_match3 . '$/';
if ( preg_match( $rgb_regexp, $color_str, $match ) ) {
- return wp_tinycolor_rgb_to_rgb(
+ $rgb = wp_tinycolor_rgb_to_rgb(
+ array(
+ 'r' => $match[1],
+ 'g' => $match[2],
+ 'b' => $match[3],
+ )
+ );
+
+ $rgb['a'] = 1;
+
+ return $rgb;
+ }
+
+ $rgba_regexp = '/^rgba' . $permissive_match4 . '$/';
+ if ( preg_match( $rgba_regexp, $color_str, $match ) ) {
+ $rgb = wp_tinycolor_rgb_to_rgb(
array(
'r' => $match[1],
'g' => $match[2],
'b' => $match[3],
)
);
- }
- $rgba_regexp = '/^rgba' . $permissive_match4 . '$/';
- if ( preg_match( $rgba_regexp, $color_str, $match ) ) {
- return wp_tinycolor_rgb_to_rgb(
- array(
- 'r' => $match[1],
- 'g' => $match[2],
- 'b' => $match[3],
- )
- );
+ $rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );
+
+ return $rgb;
}
$hsl_regexp = '/^hsl' . $permissive_match3 . '$/';
if ( preg_match( $hsl_regexp, $color_str, $match ) ) {
- return wp_tinycolor_hsl_to_rgb(
+ $rgb = wp_tinycolor_hsl_to_rgb(
array(
'h' => $match[1],
's' => $match[2],
'l' => $match[3],
)
);
+
+ $rgb['a'] = 1;
+
+ return $rgb;
}
$hsla_regexp = '/^hsla' . $permissive_match4 . '$/';
if ( preg_match( $hsla_regexp, $color_str, $match ) ) {
- return wp_tinycolor_hsl_to_rgb(
+ $rgb = wp_tinycolor_hsl_to_rgb(
array(
'h' => $match[1],
's' => $match[2],
'l' => $match[3],
)
);
+
+ $rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );
+
+ return $rgb;
}
$hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
if ( preg_match( $hex8_regexp, $color_str, $match ) ) {
- return wp_tinycolor_rgb_to_rgb(
+ $rgb = wp_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1], 16, 10 ),
'g' => base_convert( $match[2], 16, 10 ),
'b' => base_convert( $match[3], 16, 10 ),
)
);
+
+ $rgb['a'] = _wp_tinycolor_bound_alpha(
+ base_convert( $match[4], 16, 10 ) / 255
+ );
+
+ return $rgb;
}
$hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
if ( preg_match( $hex6_regexp, $color_str, $match ) ) {
- return wp_tinycolor_rgb_to_rgb(
+ $rgb = wp_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1], 16, 10 ),
'g' => base_convert( $match[2], 16, 10 ),
'b' => base_convert( $match[3], 16, 10 ),
)
);
+
+ $rgb['a'] = 1;
+
+ return $rgb;
}
$hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
if ( preg_match( $hex4_regexp, $color_str, $match ) ) {
- return wp_tinycolor_rgb_to_rgb(
+ $rgb = wp_tinycolor_rgb_to_rgb(
+ array(
+ 'r' => base_convert( $match[1] . $match[1], 16, 10 ),
+ 'g' => base_convert( $match[2] . $match[2], 16, 10 ),
+ 'b' => base_convert( $match[3] . $match[3], 16, 10 ),
+ )
+ );
+
+ $rgb['a'] = _wp_tinycolor_bound_alpha(
+ base_convert( $match[4] . $match[4], 16, 10 ) / 255
+ );
+
+ return $rgb;
+ }
+
+ $hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
+ if ( preg_match( $hex3_regexp, $color_str, $match ) ) {
+ $rgb = wp_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1] . $match[1], 16, 10 ),
'g' => base_convert( $match[2] . $match[2], 16, 10 ),
'b' => base_convert( $match[3] . $match[3], 16, 10 ),
)
);
+
+ $rgb['a'] = 1;
+
+ return $rgb;
}
- $hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
- if ( preg_match( $hex3_regexp, $color_str, $match ) ) {
- return wp_tinycolor_rgb_to_rgb(
- array(
- 'r' => base_convert( $match[1] . $match[1], 16, 10 ),
- 'g' => base_convert( $match[2] . $match[2], 16, 10 ),
- 'b' => base_convert( $match[3] . $match[3], 16, 10 ),
- )
+ /*
+ * The JS color picker considers the string "transparent" to be a hex value,
+ * so we need to handle it here as a special case.
+ */
+ if ( 'transparent' === $color_str ) {
+ return array(
+ 'r' => 0,
+ 'g' => 0,
+ 'b' => 0,
+ 'a' => 0,
);
}
}
+/**
+ * Returns the prefixed id for the duotone filter for use as a CSS id.
+ *
+ * @since 5.9.1
+ * @access private
+ *
+ * @param array $preset Duotone preset value as seen in theme.json.
+ * @return string Duotone filter CSS id.
+ */
+function wp_get_duotone_filter_id( $preset ) {
+ if ( ! isset( $preset['slug'] ) ) {
+ return '';
+ }
+
+ return 'wp-duotone-' . $preset['slug'];
+}
+
+/**
+ * Returns the CSS filter property url to reference the rendered SVG.
+ *
+ * @since 5.9.0
+ * @access private
+ *
+ * @param array $preset Duotone preset value as seen in theme.json.
+ * @return string Duotone CSS filter property url value.
+ */
+function wp_get_duotone_filter_property( $preset ) {
+ $filter_id = wp_get_duotone_filter_id( $preset );
+ return "url('#" . $filter_id . "')";
+}
+
+/**
+ * Returns the duotone filter SVG string for the preset.
+ *
+ * @since 5.9.1
+ * @access private
+ *
+ * @param array $preset Duotone preset value as seen in theme.json.
+ * @return string Duotone SVG filter.
+ */
+function wp_get_duotone_filter_svg( $preset ) {
+ $filter_id = wp_get_duotone_filter_id( $preset );
+
+ $duotone_values = array(
+ 'r' => array(),
+ 'g' => array(),
+ 'b' => array(),
+ 'a' => array(),
+ );
+
+ if ( ! isset( $preset['colors'] ) || ! is_array( $preset['colors'] ) ) {
+ $preset['colors'] = array();
+ }
+
+ foreach ( $preset['colors'] as $color_str ) {
+ $color = wp_tinycolor_string_to_rgb( $color_str );
+
+ $duotone_values['r'][] = $color['r'] / 255;
+ $duotone_values['g'][] = $color['g'] / 255;
+ $duotone_values['b'][] = $color['b'] / 255;
+ $duotone_values['a'][] = $color['a'];
+ }
+
+ ob_start();
+
+ ?>
+
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 0 0"
+ width="0"
+ height="0"
+ focusable="false"
+ role="none"
+ style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
+ >
+ <defs>
+ <filter id="<?php echo esc_attr( $filter_id ); ?>">
+ <feColorMatrix
+ color-interpolation-filters="sRGB"
+ type="matrix"
+ values="
+ .299 .587 .114 0 0
+ .299 .587 .114 0 0
+ .299 .587 .114 0 0
+ .299 .587 .114 0 0
+ "
+ />
+ <feComponentTransfer color-interpolation-filters="sRGB" >
+ <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
+ <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
+ <feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />
+ <feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" />
+ </feComponentTransfer>
+ <feComposite in2="SourceGraphic" operator="in" />
+ </filter>
+ </defs>
+ </svg>
+
+ <?php
+
+ $svg = ob_get_clean();
+
+ if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) {
+ // Clean up the whitespace.
+ $svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg );
+ $svg = preg_replace( '/> </', '><', $svg );
+ $svg = trim( $svg );
+ }
+
+ return $svg;
+}
/**
* Registers the style and colors block attributes for block types that support it.
@@ -322,7 +500,6 @@
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
- *
* @return string Filtered block content.
*/
function wp_render_duotone_support( $block_content, $block ) {
@@ -342,84 +519,61 @@
return $block_content;
}
- $duotone_colors = $block['attrs']['style']['color']['duotone'];
-
- $duotone_values = array(
- 'r' => array(),
- 'g' => array(),
- 'b' => array(),
+ $filter_preset = array(
+ 'slug' => wp_unique_id( sanitize_key( implode( '-', $block['attrs']['style']['color']['duotone'] ) . '-' ) ),
+ 'colors' => $block['attrs']['style']['color']['duotone'],
);
- foreach ( $duotone_colors as $color_str ) {
- $color = wp_tinycolor_string_to_rgb( $color_str );
+ $filter_property = wp_get_duotone_filter_property( $filter_preset );
+ $filter_id = wp_get_duotone_filter_id( $filter_preset );
+ $filter_svg = wp_get_duotone_filter_svg( $filter_preset );
- $duotone_values['r'][] = $color['r'] / 255;
- $duotone_values['g'][] = $color['g'] / 255;
- $duotone_values['b'][] = $color['b'] / 255;
+ $scope = '.' . $filter_id;
+ $selectors = explode( ',', $duotone_support );
+ $scoped = array();
+ foreach ( $selectors as $sel ) {
+ $scoped[] = $scope . ' ' . trim( $sel );
}
+ $selector = implode( ', ', $scoped );
- $duotone_id = 'wp-duotone-filter-' . uniqid();
+ // !important is needed because these styles render before global styles,
+ // and they should be overriding the duotone filters set by global styles.
+ $filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG
+ ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n"
+ : $selector . '{filter:' . $filter_property . ' !important;}';
- $selectors = explode( ',', $duotone_support );
- $selectors_scoped = array_map(
- function ( $selector ) use ( $duotone_id ) {
- return '.' . $duotone_id . ' ' . trim( $selector );
- },
- $selectors
- );
- $selectors_group = implode( ', ', $selectors_scoped );
+ wp_register_style( $filter_id, false, array(), true, true );
+ wp_add_inline_style( $filter_id, $filter_style );
+ wp_enqueue_style( $filter_id );
+
+ add_action(
+ 'wp_footer',
+ static function () use ( $filter_svg, $selector ) {
+ echo $filter_svg;
- ob_start();
-
- ?>
-
- <style>
- <?php echo $selectors_group; ?> {
- filter: url( <?php echo esc_url( '#' . $duotone_id ); ?> );
+ /*
+ * Safari renders elements incorrectly on first paint when the SVG
+ * filter comes after the content that it is filtering, so we force
+ * a repaint with a WebKit hack which solves the issue.
+ */
+ global $is_safari;
+ if ( $is_safari ) {
+ printf(
+ // Simply accessing el.offsetHeight flushes layout and style
+ // changes in WebKit without having to wait for setTimeout.
+ '<script>( function() { var el = document.querySelector( %s ); var display = el.style.display; el.style.display = "none"; el.offsetHeight; el.style.display = display; } )();</script>',
+ wp_json_encode( $selector )
+ );
+ }
}
- </style>
-
- <svg
- xmlns:xlink="http://www.w3.org/1999/xlink"
- viewBox="0 0 0 0"
- width="0"
- height="0"
- focusable="false"
- role="none"
- style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
- >
- <defs>
- <filter id="<?php echo esc_attr( $duotone_id ); ?>">
- <feColorMatrix
- type="matrix"
- <?php // phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent ?>
- values=".299 .587 .114 0 0
- .299 .587 .114 0 0
- .299 .587 .114 0 0
- 0 0 0 1 0"
- <?php // phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent ?>
- />
- <feComponentTransfer color-interpolation-filters="sRGB" >
- <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
- <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
- <feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />
- </feComponentTransfer>
- </filter>
- </defs>
- </svg>
-
- <?php
-
- $duotone = ob_get_clean();
+ );
// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
- $content = preg_replace(
+ return preg_replace(
'/' . preg_quote( 'class="', '/' ) . '/',
- 'class="' . $duotone_id . ' ',
+ 'class="' . $filter_id . ' ',
$block_content,
1
);
-
- return $content . $duotone;
}
// Register the block support.