[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |