[ 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       * Generates and prints the `@font-face` styles for the given fonts.
  66       *
  67       * @since 6.4.0
  68       *
  69       * @param array[][] $fonts Optional. The font-families and their font variations.
  70       *                         See {@see wp_print_font_faces()} for the supported fields.
  71       *                         Default empty array.
  72       */
  73  	public function generate_and_print( array $fonts ) {
  74          $fonts = $this->validate_fonts( $fonts );
  75  
  76          // Bail out if there are no fonts are given to process.
  77          if ( empty( $fonts ) ) {
  78              return;
  79          }
  80  
  81          $css = $this->get_css( $fonts );
  82  
  83          /*
  84           * The font-face CSS is contained within <style> tags and can only be interpreted
  85           * as CSS in the browser. Using wp_strip_all_tags() is sufficient escaping
  86           * to avoid malicious attempts to close </style> and open a <script>.
  87           */
  88          $css = wp_strip_all_tags( $css );
  89  
  90          // Bail out if there is no CSS to print.
  91          if ( empty( $css ) ) {
  92              return;
  93          }
  94  
  95          $processor = new WP_HTML_Tag_Processor( '<style class="wp-fonts-local"></style>' );
  96          $processor->next_tag();
  97          $processor->set_modifiable_text( "\n{$css}\n" );
  98          echo "{$processor->get_updated_html()}\n";
  99      }
 100  
 101      /**
 102       * Validates each of the font-face properties.
 103       *
 104       * @since 6.4.0
 105       *
 106       * @param array $fonts The fonts to valid.
 107       * @return array Prepared font-faces organized by provider and font-family.
 108       */
 109  	private function validate_fonts( array $fonts ) {
 110          $validated_fonts = array();
 111  
 112          foreach ( $fonts as $font_faces ) {
 113              foreach ( $font_faces as $font_face ) {
 114                  $font_face = $this->validate_font_face_declarations( $font_face );
 115                  // Skip if failed validation.
 116                  if ( false === $font_face ) {
 117                      continue;
 118                  }
 119  
 120                  $validated_fonts[] = $font_face;
 121              }
 122          }
 123  
 124          return $validated_fonts;
 125      }
 126  
 127      /**
 128       * Validates each font-face declaration (property and value pairing).
 129       *
 130       * @since 6.4.0
 131       *
 132       * @param array $font_face Font face property and value pairings to validate.
 133       * @return array|false Validated font-face on success, or false on failure.
 134       */
 135  	private function validate_font_face_declarations( array $font_face ) {
 136          $font_face = wp_parse_args( $font_face, $this->font_face_property_defaults );
 137  
 138          // Check the font-family.
 139          if ( empty( $font_face['font-family'] ) || ! is_string( $font_face['font-family'] ) ) {
 140              // @todo replace with `wp_trigger_error()`.
 141              _doing_it_wrong(
 142                  __METHOD__,
 143                  __( 'Font font-family must be a non-empty string.' ),
 144                  '6.4.0'
 145              );
 146              return false;
 147          }
 148  
 149          // Make sure that local fonts have 'src' defined.
 150          if ( empty( $font_face['src'] ) || ( ! is_string( $font_face['src'] ) && ! is_array( $font_face['src'] ) ) ) {
 151              // @todo replace with `wp_trigger_error()`.
 152              _doing_it_wrong(
 153                  __METHOD__,
 154                  __( 'Font src must be a non-empty string or an array of strings.' ),
 155                  '6.4.0'
 156              );
 157              return false;
 158          }
 159  
 160          // Validate the 'src' property.
 161          foreach ( (array) $font_face['src'] as $src ) {
 162              if ( empty( $src ) || ! is_string( $src ) ) {
 163                  // @todo replace with `wp_trigger_error()`.
 164                  _doing_it_wrong(
 165                      __METHOD__,
 166                      __( 'Each font src must be a non-empty string.' ),
 167                      '6.4.0'
 168                  );
 169                  return false;
 170              }
 171          }
 172  
 173          // Check the font-weight.
 174          if ( ! is_string( $font_face['font-weight'] ) && ! is_int( $font_face['font-weight'] ) ) {
 175              // @todo replace with `wp_trigger_error()`.
 176              _doing_it_wrong(
 177                  __METHOD__,
 178                  __( 'Font font-weight must be a properly formatted string or integer.' ),
 179                  '6.4.0'
 180              );
 181              return false;
 182          }
 183  
 184          // Check the font-display.
 185          if ( ! in_array( $font_face['font-display'], $this->valid_font_display, true ) ) {
 186              $font_face['font-display'] = $this->font_face_property_defaults['font-display'];
 187          }
 188  
 189          // Remove invalid properties.
 190          foreach ( $font_face as $property => $value ) {
 191              if ( ! in_array( $property, $this->valid_font_face_properties, true ) ) {
 192                  unset( $font_face[ $property ] );
 193              }
 194          }
 195  
 196          return $font_face;
 197      }
 198  
 199      /**
 200       * Gets the `@font-face` CSS styles for locally-hosted font files.
 201       *
 202       * This method does the following processing tasks:
 203       *    1. Orchestrates an optimized `src` (with format) for browser support.
 204       *    2. Generates the `@font-face` for all its fonts.
 205       *
 206       * @since 6.4.0
 207       *
 208       * @param array[] $font_faces The font-faces to generate @font-face CSS styles.
 209       * @return string The `@font-face` CSS styles.
 210       */
 211  	private function get_css( $font_faces ) {
 212          $css = '';
 213  
 214          foreach ( $font_faces as $font_face ) {
 215                  // Order the font's `src` items to optimize for browser support.
 216                  $font_face = $this->order_src( $font_face );
 217  
 218                  // Build the @font-face CSS for this font.
 219                  $css .= '@font-face{' . $this->build_font_face_css( $font_face ) . '}' . "\n";
 220          }
 221  
 222          // Don't print the last newline character.
 223          return rtrim( $css, "\n" );
 224      }
 225  
 226      /**
 227       * Orders `src` items to optimize for browser support.
 228       *
 229       * @since 6.4.0
 230       *
 231       * @param array $font_face Font face to process.
 232       * @return array Font-face with ordered src items.
 233       */
 234  	private function order_src( array $font_face ) {
 235          if ( ! is_array( $font_face['src'] ) ) {
 236              $font_face['src'] = (array) $font_face['src'];
 237          }
 238  
 239          $src         = array();
 240          $src_ordered = array();
 241  
 242          foreach ( $font_face['src'] as $url ) {
 243              // Add data URIs first.
 244              if ( str_starts_with( trim( $url ), 'data:' ) ) {
 245                  $src_ordered[] = array(
 246                      'url'    => $url,
 247                      'format' => 'data',
 248                  );
 249                  continue;
 250              }
 251              $format         = pathinfo( $url, PATHINFO_EXTENSION );
 252              $src[ $format ] = $url;
 253          }
 254  
 255          // Add woff2.
 256          if ( ! empty( $src['woff2'] ) ) {
 257              $src_ordered[] = array(
 258                  'url'    => $src['woff2'],
 259                  'format' => 'woff2',
 260              );
 261          }
 262  
 263          // Add woff.
 264          if ( ! empty( $src['woff'] ) ) {
 265              $src_ordered[] = array(
 266                  'url'    => $src['woff'],
 267                  'format' => 'woff',
 268              );
 269          }
 270  
 271          // Add ttf.
 272          if ( ! empty( $src['ttf'] ) ) {
 273              $src_ordered[] = array(
 274                  'url'    => $src['ttf'],
 275                  'format' => 'truetype',
 276              );
 277          }
 278  
 279          // Add eot.
 280          if ( ! empty( $src['eot'] ) ) {
 281              $src_ordered[] = array(
 282                  'url'    => $src['eot'],
 283                  'format' => 'embedded-opentype',
 284              );
 285          }
 286  
 287          // Add otf.
 288          if ( ! empty( $src['otf'] ) ) {
 289              $src_ordered[] = array(
 290                  'url'    => $src['otf'],
 291                  'format' => 'opentype',
 292              );
 293          }
 294          $font_face['src'] = $src_ordered;
 295  
 296          return $font_face;
 297      }
 298  
 299      /**
 300       * Builds the font-family's CSS.
 301       *
 302       * @since 6.4.0
 303       *
 304       * @param array $font_face Font face to process.
 305       * @return string This font-family's CSS.
 306       */
 307  	private function build_font_face_css( array $font_face ) {
 308          $css = '';
 309  
 310          /*
 311           * Wrap font-family in quotes if it contains spaces
 312           * and is not already wrapped in quotes.
 313           */
 314          if (
 315              str_contains( $font_face['font-family'], ' ' ) &&
 316              ! str_contains( $font_face['font-family'], '"' ) &&
 317              ! str_contains( $font_face['font-family'], "'" )
 318          ) {
 319              $font_face['font-family'] = '"' . $font_face['font-family'] . '"';
 320          }
 321  
 322          foreach ( $font_face as $key => $value ) {
 323              // Compile the "src" parameter.
 324              if ( 'src' === $key ) {
 325                  $value = $this->compile_src( $value );
 326              }
 327  
 328              // If font-variation-settings is an array, convert it to a string.
 329              if ( 'font-variation-settings' === $key && is_array( $value ) ) {
 330                  $value = $this->compile_variations( $value );
 331              }
 332  
 333              if ( ! empty( $value ) ) {
 334                  $css .= "$key:$value;";
 335              }
 336          }
 337  
 338          return $css;
 339      }
 340  
 341      /**
 342       * Compiles the `src` into valid CSS.
 343       *
 344       * @since 6.4.0
 345       *
 346       * @param array $value Value to process.
 347       * @return string The CSS.
 348       */
 349  	private function compile_src( array $value ) {
 350          $src = '';
 351  
 352          foreach ( $value as $item ) {
 353              $src .= ( 'data' === $item['format'] )
 354                  ? ", url({$item['url']})"
 355                  : ", url('{$item['url']}') format('{$item['format']}')";
 356          }
 357  
 358          $src = ltrim( $src, ', ' );
 359          return $src;
 360      }
 361  
 362      /**
 363       * Compiles the font variation settings.
 364       *
 365       * @since 6.4.0
 366       *
 367       * @param array $font_variation_settings Array of font variation settings.
 368       * @return string The CSS.
 369       */
 370  	private function compile_variations( array $font_variation_settings ) {
 371          $variations = '';
 372  
 373          foreach ( $font_variation_settings as $key => $value ) {
 374              $variations .= "$key $value";
 375          }
 376  
 377          return $variations;
 378      }
 379  }


Generated : Sat Apr 25 08:20:11 2026 Cross-referenced by PHPXref