[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/fonts/ -> class-wp-font-face.php (source)

   1  <?php
   2  /**
   3   * WP_Font_Face class.
   4   *
   5   * @package    WordPress
   6   * @subpackage Fonts
   7   * @since      6.4.0
   8   */
   9  
  10  /**
  11   * Font Face generates and prints `@font-face` styles for given fonts.
  12   *
  13   * @since 6.4.0
  14   */
  15  class WP_Font_Face {
  16  
  17      /**
  18       * The font-face property defaults.
  19       *
  20       * @since 6.4.0
  21       *
  22       * @var string[]
  23       */
  24      private $font_face_property_defaults = array(
  25          'font-family'  => '',
  26          'font-style'   => 'normal',
  27          'font-weight'  => '400',
  28          'font-display' => 'fallback',
  29      );
  30  
  31      /**
  32       * Valid font-face property names.
  33       *
  34       * @since 6.4.0
  35       *
  36       * @var string[]
  37       */
  38      private $valid_font_face_properties = array(
  39          'ascent-override',
  40          'descent-override',
  41          'font-display',
  42          'font-family',
  43          'font-stretch',
  44          'font-style',
  45          'font-weight',
  46          'font-variant',
  47          'font-feature-settings',
  48          'font-variation-settings',
  49          'line-gap-override',
  50          'size-adjust',
  51          'src',
  52          'unicode-range',
  53      );
  54  
  55      /**
  56       * Valid font-display values.
  57       *
  58       * @since 6.4.0
  59       *
  60       * @var string[]
  61       */
  62      private $valid_font_display = array( 'auto', 'block', 'fallback', 'swap', 'optional' );
  63  
  64      /**
  65       * Array of font-face style tag's attribute(s)
  66       * where the key is the attribute name and the
  67       * value is its value.
  68       *
  69       * @since 6.4.0
  70       *
  71       * @var string[]
  72       */
  73      private $style_tag_attrs = array();
  74  
  75      /**
  76       * Creates and initializes an instance of WP_Font_Face.
  77       *
  78       * @since 6.4.0
  79       */
  80  	public function __construct() {
  81          if (
  82              function_exists( 'is_admin' ) && ! is_admin()
  83              &&
  84              function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'style' )
  85          ) {
  86              $this->style_tag_attrs = array( 'type' => 'text/css' );
  87          }
  88      }
  89  
  90      /**
  91       * Generates and prints the `@font-face` styles for the given fonts.
  92       *
  93       * @since 6.4.0
  94       *
  95       * @param array[][] $fonts Optional. The font-families and their font variations.
  96       *                         See {@see wp_print_font_faces()} for the supported fields.
  97       *                         Default empty array.
  98       */
  99  	public function generate_and_print( array $fonts ) {
 100          $fonts = $this->validate_fonts( $fonts );
 101  
 102          // Bail out if there are no fonts are given to process.
 103          if ( empty( $fonts ) ) {
 104              return;
 105          }
 106  
 107          $css = $this->get_css( $fonts );
 108  
 109          /*
 110           * The font-face CSS is contained within <style> tags and can only be interpreted
 111           * as CSS in the browser. Using wp_strip_all_tags() is sufficient escaping
 112           * to avoid malicious attempts to close </style> and open a <script>.
 113           */
 114          $css = wp_strip_all_tags( $css );
 115  
 116          // Bail out if there is no CSS to print.
 117          if ( empty( $css ) ) {
 118              return;
 119          }
 120  
 121          printf( $this->get_style_element(), $css );
 122      }
 123  
 124      /**
 125       * Validates each of the font-face properties.
 126       *
 127       * @since 6.4.0
 128       *
 129       * @param array $fonts The fonts to valid.
 130       * @return array Prepared font-faces organized by provider and font-family.
 131       */
 132  	private function validate_fonts( array $fonts ) {
 133          $validated_fonts = array();
 134  
 135          foreach ( $fonts as $font_faces ) {
 136              foreach ( $font_faces as $font_face ) {
 137                  $font_face = $this->validate_font_face_declarations( $font_face );
 138                  // Skip if failed validation.
 139                  if ( false === $font_face ) {
 140                      continue;
 141                  }
 142  
 143                  $validated_fonts[] = $font_face;
 144              }
 145          }
 146  
 147          return $validated_fonts;
 148      }
 149  
 150      /**
 151       * Validates each font-face declaration (property and value pairing).
 152       *
 153       * @since 6.4.0
 154       *
 155       * @param array $font_face Font face property and value pairings to validate.
 156       * @return array|false Validated font-face on success, or false on failure.
 157       */
 158  	private function validate_font_face_declarations( array $font_face ) {
 159          $font_face = wp_parse_args( $font_face, $this->font_face_property_defaults );
 160  
 161          // Check the font-family.
 162          if ( empty( $font_face['font-family'] ) || ! is_string( $font_face['font-family'] ) ) {
 163              // @todo replace with `wp_trigger_error()`.
 164              _doing_it_wrong(
 165                  __METHOD__,
 166                  __( 'Font font-family must be a non-empty string.' ),
 167                  '6.4.0'
 168              );
 169              return false;
 170          }
 171  
 172          // Make sure that local fonts have 'src' defined.
 173          if ( empty( $font_face['src'] ) || ( ! is_string( $font_face['src'] ) && ! is_array( $font_face['src'] ) ) ) {
 174              // @todo replace with `wp_trigger_error()`.
 175              _doing_it_wrong(
 176                  __METHOD__,
 177                  __( 'Font src must be a non-empty string or an array of strings.' ),
 178                  '6.4.0'
 179              );
 180              return false;
 181          }
 182  
 183          // Validate the 'src' property.
 184          foreach ( (array) $font_face['src'] as $src ) {
 185              if ( empty( $src ) || ! is_string( $src ) ) {
 186                  // @todo replace with `wp_trigger_error()`.
 187                  _doing_it_wrong(
 188                      __METHOD__,
 189                      __( 'Each font src must be a non-empty string.' ),
 190                      '6.4.0'
 191                  );
 192                  return false;
 193              }
 194          }
 195  
 196          // Check the font-weight.
 197          if ( ! is_string( $font_face['font-weight'] ) && ! is_int( $font_face['font-weight'] ) ) {
 198              // @todo replace with `wp_trigger_error()`.
 199              _doing_it_wrong(
 200                  __METHOD__,
 201                  __( 'Font font-weight must be a properly formatted string or integer.' ),
 202                  '6.4.0'
 203              );
 204              return false;
 205          }
 206  
 207          // Check the font-display.
 208          if ( ! in_array( $font_face['font-display'], $this->valid_font_display, true ) ) {
 209              $font_face['font-display'] = $this->font_face_property_defaults['font-display'];
 210          }
 211  
 212          // Remove invalid properties.
 213          foreach ( $font_face as $property => $value ) {
 214              if ( ! in_array( $property, $this->valid_font_face_properties, true ) ) {
 215                  unset( $font_face[ $property ] );
 216              }
 217          }
 218  
 219          return $font_face;
 220      }
 221  
 222      /**
 223       * Gets the style element for wrapping the `@font-face` CSS.
 224       *
 225       * @since 6.4.0
 226       *
 227       * @return string The style element.
 228       */
 229  	private function get_style_element() {
 230          $attributes = $this->generate_style_element_attributes();
 231  
 232          return "<style class='wp-fonts-local'{$attributes}>\n%s\n</style>\n";
 233      }
 234  
 235      /**
 236       * Gets the defined <style> element's attributes.
 237       *
 238       * @since 6.4.0
 239       *
 240       * @return string A string of attribute=value when defined, else, empty string.
 241       */
 242  	private function generate_style_element_attributes() {
 243          $attributes = '';
 244          foreach ( $this->style_tag_attrs as $name => $value ) {
 245              $attributes .= " {$name}='{$value}'";
 246          }
 247          return $attributes;
 248      }
 249  
 250      /**
 251       * Gets the `@font-face` CSS styles for locally-hosted font files.
 252       *
 253       * This method does the following processing tasks:
 254       *    1. Orchestrates an optimized `src` (with format) for browser support.
 255       *    2. Generates the `@font-face` for all its fonts.
 256       *
 257       * @since 6.4.0
 258       *
 259       * @param array[] $font_faces The font-faces to generate @font-face CSS styles.
 260       * @return string The `@font-face` CSS styles.
 261       */
 262  	private function get_css( $font_faces ) {
 263          $css = '';
 264  
 265          foreach ( $font_faces as $font_face ) {
 266                  // Order the font's `src` items to optimize for browser support.
 267                  $font_face = $this->order_src( $font_face );
 268  
 269                  // Build the @font-face CSS for this font.
 270                  $css .= '@font-face{' . $this->build_font_face_css( $font_face ) . '}' . "\n";
 271          }
 272  
 273          // Don't print the last newline character.
 274          return rtrim( $css, "\n" );
 275      }
 276  
 277      /**
 278       * Orders `src` items to optimize for browser support.
 279       *
 280       * @since 6.4.0
 281       *
 282       * @param array $font_face Font face to process.
 283       * @return array Font-face with ordered src items.
 284       */
 285  	private function order_src( array $font_face ) {
 286          if ( ! is_array( $font_face['src'] ) ) {
 287              $font_face['src'] = (array) $font_face['src'];
 288          }
 289  
 290          $src         = array();
 291          $src_ordered = array();
 292  
 293          foreach ( $font_face['src'] as $url ) {
 294              // Add data URIs first.
 295              if ( str_starts_with( trim( $url ), 'data:' ) ) {
 296                  $src_ordered[] = array(
 297                      'url'    => $url,
 298                      'format' => 'data',
 299                  );
 300                  continue;
 301              }
 302              $format         = pathinfo( $url, PATHINFO_EXTENSION );
 303              $src[ $format ] = $url;
 304          }
 305  
 306          // Add woff2.
 307          if ( ! empty( $src['woff2'] ) ) {
 308              $src_ordered[] = array(
 309                  'url'    => $src['woff2'],
 310                  'format' => 'woff2',
 311              );
 312          }
 313  
 314          // Add woff.
 315          if ( ! empty( $src['woff'] ) ) {
 316              $src_ordered[] = array(
 317                  'url'    => $src['woff'],
 318                  'format' => 'woff',
 319              );
 320          }
 321  
 322          // Add ttf.
 323          if ( ! empty( $src['ttf'] ) ) {
 324              $src_ordered[] = array(
 325                  'url'    => $src['ttf'],
 326                  'format' => 'truetype',
 327              );
 328          }
 329  
 330          // Add eot.
 331          if ( ! empty( $src['eot'] ) ) {
 332              $src_ordered[] = array(
 333                  'url'    => $src['eot'],
 334                  'format' => 'embedded-opentype',
 335              );
 336          }
 337  
 338          // Add otf.
 339          if ( ! empty( $src['otf'] ) ) {
 340              $src_ordered[] = array(
 341                  'url'    => $src['otf'],
 342                  'format' => 'opentype',
 343              );
 344          }
 345          $font_face['src'] = $src_ordered;
 346  
 347          return $font_face;
 348      }
 349  
 350      /**
 351       * Builds the font-family's CSS.
 352       *
 353       * @since 6.4.0
 354       *
 355       * @param array $font_face Font face to process.
 356       * @return string This font-family's CSS.
 357       */
 358  	private function build_font_face_css( array $font_face ) {
 359          $css = '';
 360  
 361          /*
 362           * Wrap font-family in quotes if it contains spaces
 363           * and is not already wrapped in quotes.
 364           */
 365          if (
 366              str_contains( $font_face['font-family'], ' ' ) &&
 367              ! str_contains( $font_face['font-family'], '"' ) &&
 368              ! str_contains( $font_face['font-family'], "'" )
 369          ) {
 370              $font_face['font-family'] = '"' . $font_face['font-family'] . '"';
 371          }
 372  
 373          foreach ( $font_face as $key => $value ) {
 374              // Compile the "src" parameter.
 375              if ( 'src' === $key ) {
 376                  $value = $this->compile_src( $value );
 377              }
 378  
 379              // If font-variation-settings is an array, convert it to a string.
 380              if ( 'font-variation-settings' === $key && is_array( $value ) ) {
 381                  $value = $this->compile_variations( $value );
 382              }
 383  
 384              if ( ! empty( $value ) ) {
 385                  $css .= "$key:$value;";
 386              }
 387          }
 388  
 389          return $css;
 390      }
 391  
 392      /**
 393       * Compiles the `src` into valid CSS.
 394       *
 395       * @since 6.4.0
 396       *
 397       * @param array $value Value to process.
 398       * @return string The CSS.
 399       */
 400  	private function compile_src( array $value ) {
 401          $src = '';
 402  
 403          foreach ( $value as $item ) {
 404              $src .= ( 'data' === $item['format'] )
 405                  ? ", url({$item['url']})"
 406                  : ", url('{$item['url']}') format('{$item['format']}')";
 407          }
 408  
 409          $src = ltrim( $src, ', ' );
 410          return $src;
 411      }
 412  
 413      /**
 414       * Compiles the font variation settings.
 415       *
 416       * @since 6.4.0
 417       *
 418       * @param array $font_variation_settings Array of font variation settings.
 419       * @return string The CSS.
 420       */
 421  	private function compile_variations( array $font_variation_settings ) {
 422          $variations = '';
 423  
 424          foreach ( $font_variation_settings as $key => $value ) {
 425              $variations .= "$key $value";
 426          }
 427  
 428          return $variations;
 429      }
 430  }


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref