diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php
index e775bc9237697..28bb579efa8fb 100644
--- a/lib/block-supports/duotone.php
+++ b/lib/block-supports/duotone.php
@@ -11,11 +11,15 @@
*
* @see https://github.com/bgrins/TinyColor
*
+ * @deprecated 6.3.0
+ *
* @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 gutenberg_tinycolor_bound01( $n, $max ) {
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+
if ( 'string' === gettype( $n ) && str_contains( $n, '.' ) && 1 === (float) $n ) {
$n = '100%';
}
@@ -42,10 +46,14 @@ function gutenberg_tinycolor_bound01( $n, $max ) {
*
* @see https://github.com/bgrins/TinyColor
*
+ * @deprecated 6.3.0
+ *
* @param mixed $n Number of unknown type.
* @return float Value in the range [0,1].
*/
function gutenberg_tinycolor_bound_alpha( $n ) {
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+
if ( is_numeric( $n ) ) {
$n = (float) $n;
if ( $n >= 0 && $n <= 1 ) {
@@ -60,10 +68,14 @@ function gutenberg_tinycolor_bound_alpha( $n ) {
*
* @see https://github.com/bgrins/TinyColor
*
+ * @deprecated 6.3.0
+ *
* @param array $rgb_color RGB object.
* @return array Rounded and converted RGB object.
*/
function gutenberg_tinycolor_rgb_to_rgb( $rgb_color ) {
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+
return array(
'r' => gutenberg_tinycolor_bound01( $rgb_color['r'], 255 ) * 255,
'g' => gutenberg_tinycolor_bound01( $rgb_color['g'], 255 ) * 255,
@@ -76,12 +88,16 @@ function gutenberg_tinycolor_rgb_to_rgb( $rgb_color ) {
*
* @see https://github.com/bgrins/TinyColor
*
+ * @deprecated 6.3.0
+ *
* @param float $p first component.
* @param float $q second component.
* @param float $t third component.
* @return float R, G, or B component.
*/
function gutenberg_tinycolor_hue_to_rgb( $p, $q, $t ) {
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+
if ( $t < 0 ) {
++$t;
}
@@ -105,10 +121,14 @@ function gutenberg_tinycolor_hue_to_rgb( $p, $q, $t ) {
*
* @see https://github.com/bgrins/TinyColor
*
+ * @deprecated 6.3.0
+ *
* @param array $hsl_color HSL object.
* @return array Rounded and converted RGB object.
*/
function gutenberg_tinycolor_hsl_to_rgb( $hsl_color ) {
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+
$h = gutenberg_tinycolor_bound01( $hsl_color['h'], 360 );
$s = gutenberg_tinycolor_bound01( $hsl_color['s'], 100 );
$l = gutenberg_tinycolor_bound01( $hsl_color['l'], 100 );
@@ -140,10 +160,14 @@ function gutenberg_tinycolor_hsl_to_rgb( $hsl_color ) {
* @see https://github.com/bgrins/TinyColor
* @see https://github.com/casesandberg/react-color/
*
+ * @deprecated 6.3.0
+ *
* @param string $color_str CSS color string.
* @return array RGB object.
*/
function gutenberg_tinycolor_string_to_rgb( $color_str ) {
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+
$color_str = strtolower( trim( $color_str ) );
$css_integer = '[-\\+]?\\d+%?';
@@ -321,80 +345,14 @@ function gutenberg_get_duotone_filter_property( $preset ) {
/**
* Returns the duotone filter SVG string for the preset.
*
+ * @deprecated 6.3.0
+ *
* @param array $preset Duotone preset value as seen in theme.json.
* @return string Duotone SVG filter.
*/
function gutenberg_get_duotone_filter_svg( $preset ) {
- $filter_id = gutenberg_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 = gutenberg_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 );
- $svg = trim( $svg );
- }
-
- return $svg;
+ _deprecated_function( __FUNCTION__, '6.3.0' );
+ return WP_Duotone_Gutenberg::get_filter_svg_from_preset( $preset );
}
/**
diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php
index 4d15b0b96381c..62532ac9a6d03 100644
--- a/lib/class-wp-duotone-gutenberg.php
+++ b/lib/class-wp-duotone-gutenberg.php
@@ -2,6 +2,32 @@
/**
* WP_Duotone_Gutenberg class
*
+ * Parts of this source were derived and modified from colord,
+ * released under the MIT license.
+ *
+ * https://github.com/omgovich/colord
+ *
+ * Copyright (c) 2020 Vlad Shilov omgovich@ya.ru
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
* @package gutenberg
* @since 6.3.0
*/
@@ -73,6 +99,321 @@ class WP_Duotone_Gutenberg {
*/
const CSS_VAR_PREFIX = '--wp--preset--duotone--';
+ /**
+ * Direct port of colord's clamp function. Using min/max instead of
+ * nested ternaries.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L23
+ *
+ * @param float $number The number to clamp.
+ * @param float $min The minimum value.
+ * @param float $max The maximum value.
+ * @return float The clamped value.
+ */
+ private static function colord_clamp( $number, $min = 0, $max = 1 ) {
+ return $number > $max ? $max : ( $number > $min ? $number : $min );
+ }
+
+ /**
+ * Direct port of colord's clampHue function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L32
+ *
+ * @param float $degrees The hue to clamp.
+ * @return float The clamped hue.
+ */
+ private static function colord_clamp_hue( $degrees ) {
+ $degrees = is_finite( $degrees ) ? $degrees % 360 : 0;
+ return $degrees > 0 ? $degrees : $degrees + 360;
+ }
+
+ /**
+ * Direct port of colord's parseHue function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L40
+ *
+ * @param float $value The hue value to parse.
+ * @param string $unit The unit of the hue value.
+ * @return float The parsed hue value.
+ */
+ private static function colord_parse_hue( $value, $unit = 'deg' ) {
+ $angle_units = array(
+ 'grad' => 360 / 400,
+ 'turn' => 360,
+ 'rad' => 360 / ( M_PI * 2 ),
+ );
+
+ $factor = $angle_units[ $unit ];
+ if ( ! $factor ) {
+ $factor = 1;
+ }
+
+ return (float) $value * $factor;
+ }
+
+ /**
+ * Direct port of colord's parseHex function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hex.ts#L8
+ *
+ * @param string $hex The hex string to parse.
+ * @return array|null An array of RGBA values or null if the hex string is invalid.
+ */
+ private static function colord_parse_hex( $hex ) {
+ $is_match = preg_match(
+ '/^#([0-9a-f]{3,8})$/i',
+ $hex,
+ $hex_match
+ );
+
+ if ( ! $is_match ) {
+ return null;
+ }
+
+ $hex = $hex_match[1];
+
+ if ( 4 >= strlen( $hex ) ) {
+ return array(
+ 'r' => (int) base_convert( $hex[0] . $hex[0], 16, 10 ),
+ 'g' => (int) base_convert( $hex[1] . $hex[1], 16, 10 ),
+ 'b' => (int) base_convert( $hex[2] . $hex[2], 16, 10 ),
+ 'a' => 4 === strlen( $hex ) ? round( base_convert( $hex[3] . $hex[3], 16, 10 ) / 255, 2 ) : 1,
+ );
+ }
+
+ if ( 6 === strlen( $hex ) || 8 === strlen( $hex ) ) {
+ return array(
+ 'r' => (int) base_convert( substr( $hex, 0, 2 ), 16, 10 ),
+ 'g' => (int) base_convert( substr( $hex, 2, 2 ), 16, 10 ),
+ 'b' => (int) base_convert( substr( $hex, 4, 2 ), 16, 10 ),
+ 'a' => 8 === strlen( $hex ) ? round( (int) base_convert( substr( $hex, 6, 2 ), 16, 10 ) / 255, 2 ) : 1,
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * Direct port of colord's clampRgba function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/rgb.ts#L5
+ *
+ * @param array $rgba The RGBA array to clamp.
+ * @return array The clamped RGBA array.
+ */
+ private static function colord_clamp_rgba( $rgba ) {
+ $rgba['r'] = self::colord_clamp( $rgba['r'], 0, 255 );
+ $rgba['g'] = self::colord_clamp( $rgba['g'], 0, 255 );
+ $rgba['b'] = self::colord_clamp( $rgba['b'], 0, 255 );
+ $rgba['a'] = self::colord_clamp( $rgba['a'] );
+
+ return $rgba;
+ }
+
+ /**
+ * Direct port of colord's parseRgbaString function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/rgbString.ts#L18
+ *
+ * @param string $input The RGBA string to parse.
+ * @return array|null An array of RGBA values or null if the RGB string is invalid.
+ */
+ private static function colord_parse_rgba_string( $input ) {
+ // Functional syntax.
+ $is_match = preg_match(
+ '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i',
+ $input,
+ $match
+ );
+
+ if ( ! $is_match ) {
+ // Whitespace syntax.
+ $is_match = preg_match(
+ '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i',
+ $input,
+ $match
+ );
+ }
+
+ if ( ! $is_match ) {
+ return null;
+ }
+
+ // For some reason, preg_match doesn't include empty matches at the end
+ // of the array, so we add them manually to make things easier later.
+ for ( $i = 1; $i <= 8; $i++ ) {
+ if ( ! isset( $match[ $i ] ) ) {
+ $match[ $i ] = '';
+ }
+ }
+
+ if ( $match[2] !== $match[4] || $match[4] !== $match[6] ) {
+ return null;
+ }
+
+ return self::colord_clamp_rgba(
+ array(
+ 'r' => (float) $match[1] / ( $match[2] ? 100 / 255 : 1 ),
+ 'g' => (float) $match[3] / ( $match[4] ? 100 / 255 : 1 ),
+ 'b' => (float) $match[5] / ( $match[6] ? 100 / 255 : 1 ),
+ 'a' => '' === $match[7] ? 1 : (float) $match[7] / ( $match[8] ? 100 : 1 ),
+ )
+ );
+ }
+
+ /**
+ * Direct port of colord's clampHsla function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L6
+ *
+ * @param array $hsla The HSLA array to clamp.
+ * @return array The clamped HSLA array.
+ */
+ private static function colord_clamp_hsla( $hsla ) {
+ $hsla['h'] = self::colord_clamp_hue( $hsla['h'] );
+ $hsla['s'] = self::colord_clamp( $hsla['s'], 0, 100 );
+ $hsla['l'] = self::colord_clamp( $hsla['l'], 0, 100 );
+ $hsla['a'] = self::colord_clamp( $hsla['a'] );
+
+ return $hsla;
+ }
+
+ /**
+ * Direct port of colord's hsvaToRgba function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsv.ts#L52
+ *
+ * @param array $hsva The HSVA array to convert.
+ * @return array The RGBA array.
+ */
+ private static function colord_hsva_to_rgba( $hsva ) {
+ $h = ( $hsva['h'] / 360 ) * 6;
+ $s = $hsva['s'] / 100;
+ $v = $hsva['v'] / 100;
+ $a = $hsva['a'];
+
+ $hh = floor( $h );
+ $b = $v * ( 1 - $s );
+ $c = $v * ( 1 - ( $h - $hh ) * $s );
+ $d = $v * ( 1 - ( 1 - $h + $hh ) * $s );
+ $module = $hh % 6;
+
+ return array(
+ 'r' => array( $v, $c, $b, $b, $d, $v )[ $module ] * 255,
+ 'g' => array( $d, $v, $v, $c, $b, $b )[ $module ] * 255,
+ 'b' => array( $b, $b, $d, $v, $v, $c )[ $module ] * 255,
+ 'a' => $a,
+ );
+ }
+
+ /**
+ * Direct port of colord's hslaToHsva function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L33
+ *
+ * @param array $hsla The HSLA array to convert.
+ * @return array The HSVA array.
+ */
+ private static function colord_hsla_to_hsva( $hsla ) {
+ $h = $hsla['h'];
+ $s = $hsla['s'];
+ $l = $hsla['l'];
+ $a = $hsla['a'];
+
+ $s *= ( $l < 50 ? $l : 100 - $l ) / 100;
+
+ return array(
+ 'h' => $h,
+ 's' => $s > 0 ? ( ( 2 * $s ) / ( $l + $s ) ) * 100 : 0,
+ 'v' => $l + $s,
+ 'a' => $a,
+ );
+ }
+
+ /**
+ * Direct port of colord's hslaToRgba function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L55
+ *
+ * @param array $hsla The HSLA array to convert.
+ * @return array The RGBA array.
+ */
+ private static function colord_hsla_to_rgba( $hsla ) {
+ return self::colord_hsva_to_rgba( self::colord_hsla_to_hsva( $hsla ) );
+ }
+
+ /**
+ * Direct port of colord's parseHslaString function.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hslString.ts#L17
+ *
+ * @param string $input The HSLA string to parse.
+ * @return array|null An array of RGBA values or null if the RGB string is invalid.
+ */
+ private static function colord_parse_hsla_string( $input ) {
+ // Functional syntax.
+ $is_match = preg_match(
+ '/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s*,\s*([+-]?\d*\.?\d+)%\s*,\s*([+-]?\d*\.?\d+)%\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i',
+ $input,
+ $match
+ );
+
+ if ( ! $is_match ) {
+ // Whitespace syntax.
+ $is_match = preg_match(
+ '/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s+([+-]?\d*\.?\d+)%\s+([+-]?\d*\.?\d+)%\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i',
+ $input,
+ $match
+ );
+ }
+
+ if ( ! $is_match ) {
+ return null;
+ }
+
+ // For some reason, preg_match doesn't include empty matches at the end
+ // of the array, so we add them manually to make things easier later.
+ for ( $i = 1; $i <= 6; $i++ ) {
+ if ( ! isset( $match[ $i ] ) ) {
+ $match[ $i ] = '';
+ }
+ }
+
+ $hsla = self::colord_clamp_hsla(
+ array(
+ 'h' => self::colord_parse_hue( $match[1], $match[2] ),
+ 's' => (float) $match[3],
+ 'l' => (float) $match[4],
+ 'a' => '' === $match[5] ? 1 : (float) $match[5] / ( $match[6] ? 100 : 1 ),
+ )
+ );
+
+ return self::colord_hsla_to_rgba( $hsla );
+ }
+
+ /**
+ * Direct port of colord's parse function simplified for our use case. This
+ * version only supports string parsing and only returns RGBA values.
+ *
+ * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/parse.ts#L37
+ *
+ * @param string $input The string to parse.
+ * @return array|null An array of RGBA values or null if the string is invalid.
+ */
+ private static function colord_parse( $input ) {
+ $result = self::colord_parse_hex( $input );
+
+ if ( ! $result ) {
+ $result = self::colord_parse_rgba_string( $input );
+ }
+
+ if ( ! $result ) {
+ $result = self::colord_parse_hsla_string( $input );
+ }
+
+ return $result;
+ }
+
/**
* Get all possible duotone presets from global and theme styles and store as slug => [ colors array ]
* We only want to process this one time. On block render we'll access and output only the needed presets for that page.
@@ -160,6 +501,80 @@ private static function get_css_custom_property_name( $slug ) {
return self::CSS_VAR_PREFIX . $slug;
}
+ /**
+ * Gets the SVG for the duotone filter definition.
+ *
+ * @param string $filter_id The ID of the filter.
+ * @param array $colors An array of color strings.
+ * @return string An SVG with a duotone filter definition.
+ */
+ private static function get_filter_svg( $filter_id, $colors ) {
+ $duotone_values = array(
+ 'r' => array(),
+ 'g' => array(),
+ 'b' => array(),
+ 'a' => array(),
+ );
+
+ foreach ( $colors as $color_str ) {
+ $color = self::colord_parse( $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 );
+ $svg = trim( $svg );
+ }
+
+ return $svg;
+ }
+
/**
* Get the CSS variable for a duotone preset.
*
@@ -190,7 +605,7 @@ public static function output_footer_assets() {
foreach ( self::$output as $filter_data ) {
// SVG will be output on the page later.
- $filter_svg = gutenberg_get_duotone_filter_svg( $filter_data );
+ $filter_svg = self::get_filter_svg_from_preset( $filter_data );
echo $filter_svg;
@@ -213,7 +628,7 @@ public static function add_editor_settings( $settings ) {
$duotone_svgs = '';
$duotone_css = 'body{';
foreach ( self::$global_styles_presets as $filter_data ) {
- $duotone_svgs .= gutenberg_get_duotone_filter_svg( $filter_data );
+ $duotone_svgs .= self::get_filter_svg_from_preset( $filter_data );
$duotone_css .= self::get_css_custom_property_declaration( $filter_data );
}
$duotone_css .= '}';
@@ -444,4 +859,16 @@ public static function migrate_experimental_duotone_support_flag( $settings, $me
return $settings;
}
+
+ /**
+ * Gets the SVG for the duotone filter definition from a preset.
+ *
+ * @param array $preset The duotone preset.
+ * @return string The SVG for the filter definition.
+ */
+ public static function get_filter_svg_from_preset( $preset ) {
+ // TODO: This function will be refactored out in a follow-up PR where it will be deprecated.
+ $filter_id = gutenberg_get_duotone_filter_id( $preset );
+ return self::get_filter_svg( $filter_id, $preset['colors'] );
+ }
}