| [ 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 * 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Apr 25 08:20:11 2026 | Cross-referenced by PHPXref |