[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/block-supports/ -> duotone.php (source)

   1  <?php
   2  /**
   3   * Duotone block support flag.
   4   *
   5   * Parts of this source were derived and modified from TinyColor,
   6   * released under the MIT license.
   7   *
   8   * https://github.com/bgrins/TinyColor
   9   *
  10   * Copyright (c), Brian Grinstead, http://briangrinstead.com
  11   *
  12   * Permission is hereby granted, free of charge, to any person obtaining
  13   * a copy of this software and associated documentation files (the
  14   * "Software"), to deal in the Software without restriction, including
  15   * without limitation the rights to use, copy, modify, merge, publish,
  16   * distribute, sublicense, and/or sell copies of the Software, and to
  17   * permit persons to whom the Software is furnished to do so, subject to
  18   * the following conditions:
  19   *
  20   * The above copyright notice and this permission notice shall be
  21   * included in all copies or substantial portions of the Software.
  22   *
  23   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  27   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  28   * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  29   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  30   *
  31   * @package WordPress
  32   * @since 5.8.0
  33   */
  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 maintain
  39   * consistency with TinyColor.
  40   *
  41   * @see https://github.com/bgrins/TinyColor
  42   *
  43   * @since 5.8.0
  44   * @access private
  45   *
  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 ) && false !== strpos( $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 ) && false !== strpos( $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 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.
  95   *
  96   * Direct port of TinyColor's function, lightly simplified to maintain
  97   * consistency with TinyColor.
  98   *
  99   * @see https://github.com/bgrins/TinyColor
 100   *
 101   * @since 5.8.0
 102   * @access private
 103   *
 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 maintain
 119   * consistency with TinyColor.
 120   *
 121   * @see https://github.com/bgrins/TinyColor
 122   *
 123   * @since 5.8.0
 124   * @access private
 125   *
 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 += 1;
 134      }
 135      if ( $t > 1 ) {
 136          $t -= 1;
 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 maintain
 154   * consistency with TinyColor.
 155   *
 156   * @see https://github.com/bgrins/TinyColor
 157   *
 158   * @since 5.8.0
 159   * @access private
 160   *
 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.2
 191   * used in the JavaScript. Only colors output from react-color are implemented.
 192   *
 193   * Direct port of TinyColor's function, lightly simplified to maintain
 194   * consistency with TinyColor.
 195   *
 196   * @see https://github.com/bgrins/TinyColor
 197   * @see https://github.com/casesandberg/react-color/
 198   *
 199   * @since 5.8.0
 200   * @since 5.9.0 Added alpha processing.
 201   * @access private
 202   *
 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 ) / 255
 289          );
 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 ) / 255
 321          );
 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.1
 359   * @access private
 360   *
 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.0
 376   * @access private
 377   *
 378   * @param array $preset Duotone preset value as seen in theme.json.
 379   * @return string Duotone CSS filter property url value.
 380   */
 381  function wp_get_duotone_filter_property( $preset ) {
 382      $filter_id = wp_get_duotone_filter_id( $preset );
 383      return "url('#" . $filter_id . "')";
 384  }
 385  
 386  /**
 387   * Returns the duotone filter SVG string for the preset.
 388   *
 389   * @since 5.9.1
 390   * @access private
 391   *
 392   * @param array $preset Duotone preset value as seen in theme.json.
 393   * @return string Duotone SVG filter.
 394   */
 395  function wp_get_duotone_filter_svg( $preset ) {
 396      $filter_id = wp_get_duotone_filter_id( $preset );
 397  
 398      $duotone_values = array(
 399          'r' => array(),
 400          'g' => array(),
 401          'b' => array(),
 402          'a' => array(),
 403      );
 404  
 405      if ( ! isset( $preset['colors'] ) || ! is_array( $preset['colors'] ) ) {
 406          $preset['colors'] = array();
 407      }
 408  
 409      foreach ( $preset['colors'] as $color_str ) {
 410          $color = wp_tinycolor_string_to_rgb( $color_str );
 411  
 412          $duotone_values['r'][] = $color['r'] / 255;
 413          $duotone_values['g'][] = $color['g'] / 255;
 414          $duotone_values['b'][] = $color['b'] / 255;
 415          $duotone_values['a'][] = $color['a'];
 416      }
 417  
 418      ob_start();
 419  
 420      ?>
 421  
 422      <svg
 423          xmlns="http://www.w3.org/2000/svg"
 424          viewBox="0 0 0 0"
 425          width="0"
 426          height="0"
 427          focusable="false"
 428          role="none"
 429          style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
 430      >
 431          <defs>
 432              <filter id="<?php echo esc_attr( $filter_id ); ?>">
 433                  <feColorMatrix
 434                      color-interpolation-filters="sRGB"
 435                      type="matrix"
 436                      values="
 437                          .299 .587 .114 0 0
 438                          .299 .587 .114 0 0
 439                          .299 .587 .114 0 0
 440                          .299 .587 .114 0 0
 441                      "
 442                  />
 443                  <feComponentTransfer color-interpolation-filters="sRGB" >
 444                      <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
 445                      <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
 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'] ) ); ?>" />
 448                  </feComponentTransfer>
 449                  <feComposite in2="SourceGraphic" operator="in" />
 450              </filter>
 451          </defs>
 452      </svg>
 453  
 454      <?php
 455  
 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      );
 569  
 570      // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
 571      return preg_replace(
 572          '/' . preg_quote( 'class="', '/' ) . '/',
 573          'class="' . $filter_id . ' ',
 574          $block_content,
 575          1
 576      );
 577  }
 578  
 579  // Register the block support.
 580  WP_Block_Supports::get_instance()->register(
 581      'duotone',
 582      array(
 583          'register_attribute' => 'wp_register_duotone_support',
 584      )
 585  );
 586  add_filter( 'render_block', 'wp_render_duotone_support', 10, 2 );


Generated: Thu Nov 21 01:00:03 2024 Cross-referenced by PHPXref 0.7.1