diff --git a/wp-includes/block-supports/duotone.php b/wp-includes/block-supports/duotone.php index f73988e332..992e014dfc 100644 --- a/wp-includes/block-supports/duotone.php +++ b/wp-includes/block-supports/duotone.php @@ -352,55 +352,61 @@ function wp_tinycolor_string_to_rgb( $color_str ) { } } - /** - * Registers the style and colors block attributes for block types that support it. + * Returns the prefixed id for the duotone filter for use as a CSS id. * - * @since 5.8.0 + * @since 5.9.1 * @access private * - * @param WP_Block_Type $block_type Block Type. + * @param array $preset Duotone preset value as seen in theme.json. + * @return string Duotone filter CSS id. */ -function wp_register_duotone_support( $block_type ) { - $has_duotone_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); +function wp_get_duotone_filter_id( $preset ) { + if ( ! isset( $preset['slug'] ) ) { + return ''; } - if ( $has_duotone_support ) { - if ( ! $block_type->attributes ) { - $block_type->attributes = array(); - } - - if ( ! array_key_exists( 'style', $block_type->attributes ) ) { - $block_type->attributes['style'] = array( - 'type' => 'object', - ); - } - } + return 'wp-duotone-' . $preset['slug']; } /** - * Renders the duotone filter SVG and returns the CSS filter property to - * reference the rendered SVG. + * 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 CSS filter property. + * @return string Duotone SVG filter. */ -function wp_render_duotone_filter_preset( $preset ) { - $duotone_id = $preset['slug']; - $duotone_colors = $preset['colors']; - $filter_id = 'wp-duotone-' . $duotone_id; +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(), ); - foreach ( $duotone_colors as $color_str ) { + + 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; @@ -456,17 +462,34 @@ function wp_render_duotone_filter_preset( $preset ) { $svg = trim( $svg ); } - add_action( - // Safari doesn't render SVG filters defined in data URIs, - // and SVG filters won't render in the head of a document, - // so the next best place to put the SVG is in the footer. - is_admin() ? 'admin_footer' : 'wp_footer', - function () use ( $svg ) { - echo $svg; + return $svg; +} + +/** + * Registers the style and colors block attributes for block types that support it. + * + * @since 5.8.0 + * @access private + * + * @param WP_Block_Type $block_type Block Type. + */ +function wp_register_duotone_support( $block_type ) { + $has_duotone_support = false; + if ( property_exists( $block_type, 'supports' ) ) { + $has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + } + + if ( $has_duotone_support ) { + if ( ! $block_type->attributes ) { + $block_type->attributes = array(); } - ); - return "url('#" . $filter_id . "')"; + if ( ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } + } } /** @@ -500,8 +523,9 @@ function wp_render_duotone_support( $block_content, $block ) { 'slug' => uniqid(), 'colors' => $block['attrs']['style']['color']['duotone'], ); - $filter_property = wp_render_duotone_filter_preset( $filter_preset ); - $filter_id = 'wp-duotone-' . $filter_preset['slug']; + $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 ); $scope = '.' . $filter_id; $selectors = explode( ',', $duotone_support ); @@ -521,6 +545,28 @@ function wp_render_duotone_support( $block_content, $block ) { 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; + + /* + * 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. + '', + wp_json_encode( $selector ) + ); + } + } + ); + // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. return preg_replace( '/' . preg_quote( 'class="', '/' ) . '/', @@ -538,3 +584,21 @@ function wp_render_duotone_support( $block_content, $block ) { ) ); add_filter( 'render_block', 'wp_render_duotone_support', 10, 2 ); + +/** + * Render the SVG filters supplied by theme.json. + * + * Note that this doesn't render the per-block user-defined + * filters which are handled by wp_render_duotone_support, + * but it should be rendered in the same location as those to satisfy + * Safari's rendering quirks. + * + * @since 5.9.1 + */ +function wp_global_styles_render_svg_filters() { + $filters = wp_get_global_styles_svg_filters(); + if ( ! empty( $filters ) ) { + echo $filters; + } +} +add_action( 'wp_body_open', 'wp_global_styles_render_svg_filters' ); diff --git a/wp-includes/class-wp-theme-json.php b/wp-includes/class-wp-theme-json.php index 97b27979d4..1d11627cda 100644 --- a/wp-includes/class-wp-theme-json.php +++ b/wp-includes/class-wp-theme-json.php @@ -132,7 +132,7 @@ class WP_Theme_JSON { 'path' => array( 'color', 'duotone' ), 'override' => true, 'use_default_names' => false, - 'value_func' => 'wp_render_duotone_filter_preset', + 'value_func' => 'wp_get_duotone_filter_property', 'css_vars' => '--wp--preset--duotone--$slug', 'classes' => array(), 'properties' => array( 'filter' ), @@ -1586,6 +1586,40 @@ public function merge( $incoming ) { } } + /** + * Converts all filter (duotone) presets into SVGs. + * + * @since 5.9.1 + * + * @param array $origins List of origins to process. + * @return string SVG filters. + */ + public function get_svg_filters( $origins ) { + $blocks_metadata = static::get_blocks_metadata(); + $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); + + foreach ( $setting_nodes as $metadata ) { + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + if ( empty( $node['color']['duotone'] ) ) { + continue; + } + + $duotone_presets = $node['color']['duotone']; + + $filters = ''; + foreach ( $origins as $origin ) { + if ( ! isset( $duotone_presets[ $origin ] ) ) { + continue; + } + foreach ( $duotone_presets[ $origin ] as $duotone_preset ) { + $filters .= wp_get_duotone_filter_svg( $duotone_preset ); + } + } + } + + return $filters; + } + /** * Returns whether a presets should be overridden or not. * diff --git a/wp-includes/deprecated.php b/wp-includes/deprecated.php index 7f29f9dcb6..36b7dacd55 100644 --- a/wp-includes/deprecated.php +++ b/wp-includes/deprecated.php @@ -4208,3 +4208,20 @@ function _excerpt_render_inner_columns_blocks( $columns, $allowed_blocks ) { _deprecated_function( __FUNCTION__, '5.8.0', '_excerpt_render_inner_blocks()' ); return _excerpt_render_inner_blocks( $columns, $allowed_blocks ); } + +/** + * Renders the duotone filter SVG and returns the CSS filter property to + * reference the rendered SVG. + * + * @since 5.9.0 + * @deprecated 5.9.1 Use `wp_get_duotone_filter_property` introduced in 5.9.1. + * + * @see wp_get_duotone_filter_property() + * + * @param array $preset Duotone preset value as seen in theme.json. + * @return string Duotone CSS filter property. + */ +function wp_render_duotone_filter_preset( $preset ) { + _deprecated_function( __FUNCTION__, '5.9.1', 'wp_get_duotone_filter_property()' ); + return wp_get_duotone_filter_property( $preset ); +} diff --git a/wp-includes/global-styles-and-settings.php b/wp-includes/global-styles-and-settings.php index 0e382fe483..efc2edcaaa 100644 --- a/wp-includes/global-styles-and-settings.php +++ b/wp-includes/global-styles-and-settings.php @@ -150,3 +150,45 @@ function wp_get_global_stylesheet( $types = array() ) { return $stylesheet; } + +/** + * Returns a string containing the SVGs to be referenced as filters (duotone). + * + * @since 5.9.1 + * + * @return string + */ +function wp_get_global_styles_svg_filters() { + // Return cached value if it can be used and exists. + // It's cached by theme to make sure that theme switching clears the cache. + $can_use_cached = ( + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && + ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && + ! is_admin() + ); + $transient_name = 'global_styles_svg_filters_' . get_stylesheet(); + if ( $can_use_cached ) { + $cached = get_transient( $transient_name ); + if ( $cached ) { + return $cached; + } + } + + $supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support(); + + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); + } + + $tree = WP_Theme_JSON_Resolver::get_merged_data(); + $svgs = $tree->get_svg_filters( $origins ); + + if ( $can_use_cached ) { + // Cache for a minute, same as wp_get_global_stylesheet. + set_transient( $transient_name, $svgs, MINUTE_IN_SECONDS ); + } + + return $svgs; +} diff --git a/wp-includes/version.php b/wp-includes/version.php index 5649866a0c..f9a0330348 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '5.9.1-alpha-52758'; +$wp_version = '5.9.1-alpha-52759'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.