Make WordPress Core

Changeset 56500


Ignore:
Timestamp:
08/31/2023 09:47:40 PM (3 years ago)
Author:
hellofromTonya
Message:

Introduce font-face styles generator and printer.

Introducing Font Face, a server-side @font-face styles generator and printer.

tl;dr:

  • Introduces Font Face.
  • Deprecates _wp_theme_json_webfonts_handler().

Introduce Font Face

From an array of fonts (i.e. each font-family and its font variations to be processed), it:

  1. Validates each font-face declaration, i.e. the CSS property and value pairing. If validation fails, processing stops with no font-face styles printed.
  2. Generates the @font-face CSS for each font-family.
  3. Prints the CSS within a <style id="wp-fonts-local"> element.

The entry point into Font Face is through a new global function called wp_print_font_faces(), which is automatically called:

  • when the 'wp_head' hook runs (for the front-end).
  • when the 'admin_print_styles' hook runs (for the back-end).
  • when _wp_get_iframed_editor_assets() runs to inject the @font-face styles into the iframed editor.

Once called, it gets the fonts from Theme_JSON merged data layer, which includes theme defined fonts and user activated fonts (once the Font Library #59166 is introduced into Core).

For classic sites, themes and plugins can directly call wp_print_font_faces() and pass their fonts array to it for processing.

Deprecates _wp_theme_json_webfonts_handler().

As Font Face is a direct replacement, the stopgap code in _wp_theme_json_webfonts_handler() (introduced in 6.0.0 via [53282]) is deprecated and unused in Core.

Props note:
There's a long multiple year history baked into Font Face, which dates back to the early versions of a web font API (see #46370 and ​roadmap. The props list includes those who contributed from those early versions up to this commit.

References:

Follow-up to [53282].

Props aristath, jonoaldersonwp, hellofromTonya, andraganescu, annezazu, antonvlasenko, arena, askdesign, azaozz, bph, bradley2083, colorful-tones, costdev, davidbaumwald, desrosj, dingo_d, djcowan, domainsupport, dryanpress, elmastudio, flixos90, francina, garrett-eclipse, gigitux, grantmkin, grapplerulrich, gziolo, ironprogrammer, jb510, jeffpaul, jeremyyip, jffng, joostdevalk, jorgefilipecosta, juanmaguitar, mamaduka, matveb, mburridge, mitogh, ndiego, ntsekouras, oandregal, ocean90, oglekler, paaljoachim, pagelab, peterwilsoncc, poena, priethor, scruffian, SergeyBiryukov, shiloey, simison, skorasaurus, soean, westonruter, wildworks, zaguiini.
Fixes #59165.

Location:
trunk
Files:
42 added
1 deleted
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/phpcs.xml.dist

    r56392 r56500  
    260260                                <element value="WP_Tests_Image_Resize_UnitTestCase"/>
    261261                                <element value="WP_Theme_UnitTestCase"/>
     262                                <element value="WP_Font_Face_UnitTestCase"/>
    262263
    263264                                <!-- Mock classes. -->
  • trunk/src/wp-admin/includes/admin-filters.php

    r56199 r56500  
    169169// Append '(Draft)' to draft page titles in the privacy page dropdown.
    170170add_filter( 'list_pages', '_wp_privacy_settings_filter_draft_page_titles', 10, 2 );
     171
     172// Font management.
     173add_action( 'admin_print_styles', 'wp_print_font_faces', 50 );
  • trunk/src/wp-includes/block-editor.php

    r56047 r56500  
    362362        ob_start();
    363363        wp_print_styles();
     364        wp_print_font_faces();
    364365        $styles = ob_get_clean();
    365366
  • trunk/src/wp-includes/default-filters.php

    r56140 r56500  
    359359add_action( 'after_switch_theme', '_wp_sidebars_changed' );
    360360add_action( 'wp_print_styles', 'print_emoji_styles' );
    361 add_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' );
    362361
    363362if ( isset( $_GET['replytocom'] ) ) {
    … …  
    720719add_action( 'init', 'wp_create_initial_post_meta' );
    721720
     721// Font management.
     722add_action( 'wp_head', 'wp_print_font_faces', 50 );
     723
    722724unset( $filter, $action );
  • trunk/src/wp-includes/deprecated.php

    r56249 r56500  
    53685368        return $colors;
    53695369}
     5370
     5371/**
     5372 * Runs the theme.json webfonts handler.
     5373 *
     5374 * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined
     5375 * in the `theme.json` for the current selection and style
     5376 * variations, validates the font-face properties, generates
     5377 * the '@font-face' style declarations, and then enqueues the
     5378 * styles for both the editor and front-end.
     5379 *
     5380 * Design Notes:
     5381 * This is not a public API, but rather an internal handler.
     5382 * A future public Webfonts API will replace this stopgap code.
     5383 *
     5384 * This code design is intentional.
     5385 *    a. It hides the inner-workings.
     5386 *    b. It does not expose API ins or outs for consumption.
     5387 *    c. It only works with a theme's `theme.json`.
     5388 *
     5389 * Why?
     5390 *    a. To avoid backwards-compatibility issues when
     5391 *       the Webfonts API is introduced in Core.
     5392 *    b. To make `fontFace` declarations in `theme.json` work.
     5393 *
     5394 * @link  https://github.com/WordPress/gutenberg/issues/40472
     5395 *
     5396 * @since 6.0.0
     5397 * @deprecated 6.4.0 Use wp_print_font_faces() instead.
     5398 * @access private
     5399 */
     5400function _wp_theme_json_webfonts_handler() {
     5401        _deprecated_function( __FUNCTION__, '6.4.0', 'wp_print_font_faces' );
     5402
     5403        // Block themes are unavailable during installation.
     5404        if ( wp_installing() ) {
     5405                return;
     5406        }
     5407
     5408        if ( ! wp_theme_has_theme_json() ) {
     5409                return;
     5410        }
     5411
     5412        // Webfonts to be processed.
     5413        $registered_webfonts = array();
     5414
     5415        /**
     5416         * Gets the webfonts from theme.json.
     5417         *
     5418         * @since 6.0.0
     5419         *
     5420         * @return array Array of defined webfonts.
     5421         */
     5422        $fn_get_webfonts_from_theme_json = static function() {
     5423                // Get settings from theme.json.
     5424                $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings();
     5425
     5426                // If in the editor, add webfonts defined in variations.
     5427                if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
     5428                        $variations = WP_Theme_JSON_Resolver::get_style_variations();
     5429                        foreach ( $variations as $variation ) {
     5430                                // Skip if fontFamilies are not defined in the variation.
     5431                                if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) {
     5432                                        continue;
     5433                                }
     5434
     5435                                // Initialize the array structure.
     5436                                if ( empty( $settings['typography'] ) ) {
     5437                                        $settings['typography'] = array();
     5438                                }
     5439                                if ( empty( $settings['typography']['fontFamilies'] ) ) {
     5440                                        $settings['typography']['fontFamilies'] = array();
     5441                                }
     5442                                if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) {
     5443                                        $settings['typography']['fontFamilies']['theme'] = array();
     5444                                }
     5445
     5446                                // Combine variations with settings. Remove duplicates.
     5447                                $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] );
     5448                                $settings['typography']['fontFamilies']          = array_unique( $settings['typography']['fontFamilies'] );
     5449                        }
     5450                }
     5451
     5452                // Bail out early if there are no settings for webfonts.
     5453                if ( empty( $settings['typography']['fontFamilies'] ) ) {
     5454                        return array();
     5455                }
     5456
     5457                $webfonts = array();
     5458
     5459                // Look for fontFamilies.
     5460                foreach ( $settings['typography']['fontFamilies'] as $font_families ) {
     5461                        foreach ( $font_families as $font_family ) {
     5462
     5463                                // Skip if fontFace is not defined.
     5464                                if ( empty( $font_family['fontFace'] ) ) {
     5465                                        continue;
     5466                                }
     5467
     5468                                // Skip if fontFace is not an array of webfonts.
     5469                                if ( ! is_array( $font_family['fontFace'] ) ) {
     5470                                        continue;
     5471                                }
     5472
     5473                                $webfonts = array_merge( $webfonts, $font_family['fontFace'] );
     5474                        }
     5475                }
     5476
     5477                return $webfonts;
     5478        };
     5479
     5480        /**
     5481         * Transforms each 'src' into an URI by replacing 'file:./'
     5482         * placeholder from theme.json.
     5483         *
     5484         * The absolute path to the webfont file(s) cannot be defined in
     5485         * theme.json. `file:./` is the placeholder which is replaced by
     5486         * the theme's URL path to the theme's root.
     5487         *
     5488         * @since 6.0.0
     5489         *
     5490         * @param array $src Webfont file(s) `src`.
     5491         * @return array Webfont's `src` in URI.
     5492         */
     5493        $fn_transform_src_into_uri = static function( array $src ) {
     5494                foreach ( $src as $key => $url ) {
     5495                        // Tweak the URL to be relative to the theme root.
     5496                        if ( ! str_starts_with( $url, 'file:./' ) ) {
     5497                                continue;
     5498                        }
     5499
     5500                        $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) );
     5501                }
     5502
     5503                return $src;
     5504        };
     5505
     5506        /**
     5507         * Converts the font-face properties (i.e. keys) into kebab-case.
     5508         *
     5509         * @since 6.0.0
     5510         *
     5511         * @param array $font_face Font face to convert.
     5512         * @return array Font faces with each property in kebab-case format.
     5513         */
     5514        $fn_convert_keys_to_kebab_case = static function( array $font_face ) {
     5515                foreach ( $font_face as $property => $value ) {
     5516                        $kebab_case               = _wp_to_kebab_case( $property );
     5517                        $font_face[ $kebab_case ] = $value;
     5518                        if ( $kebab_case !== $property ) {
     5519                                unset( $font_face[ $property ] );
     5520                        }
     5521                }
     5522
     5523                return $font_face;
     5524        };
     5525
     5526        /**
     5527         * Validates a webfont.
     5528         *
     5529         * @since 6.0.0
     5530         *
     5531         * @param array $webfont The webfont arguments.
     5532         * @return array|false The validated webfont arguments, or false if the webfont is invalid.
     5533         */
     5534        $fn_validate_webfont = static function( $webfont ) {
     5535                $webfont = wp_parse_args(
     5536                                $webfont,
     5537                                array(
     5538                                                'font-family'  => '',
     5539                                                'font-style'   => 'normal',
     5540                                                'font-weight'  => '400',
     5541                                                'font-display' => 'fallback',
     5542                                                'src'          => array(),
     5543                                )
     5544                );
     5545
     5546                // Check the font-family.
     5547                if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) {
     5548                        trigger_error( __( 'Webfont font family must be a non-empty string.' ) );
     5549
     5550                        return false;
     5551                }
     5552
     5553                // Check that the `src` property is defined and a valid type.
     5554                if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) {
     5555                        trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) );
     5556
     5557                        return false;
     5558                }
     5559
     5560                // Validate the `src` property.
     5561                foreach ( (array) $webfont['src'] as $src ) {
     5562                        if ( ! is_string( $src ) || '' === trim( $src ) ) {
     5563                                trigger_error( __( 'Each webfont src must be a non-empty string.' ) );
     5564
     5565                                return false;
     5566                        }
     5567                }
     5568
     5569                // Check the font-weight.
     5570                if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) {
     5571                        trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) );
     5572
     5573                        return false;
     5574                }
     5575
     5576                // Check the font-display.
     5577                if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'optional', 'swap' ), true ) ) {
     5578                        $webfont['font-display'] = 'fallback';
     5579                }
     5580
     5581                $valid_props = array(
     5582                                'ascend-override',
     5583                                'descend-override',
     5584                                'font-display',
     5585                                'font-family',
     5586                                'font-stretch',
     5587                                'font-style',
     5588                                'font-weight',
     5589                                'font-variant',
     5590                                'font-feature-settings',
     5591                                'font-variation-settings',
     5592                                'line-gap-override',
     5593                                'size-adjust',
     5594                                'src',
     5595                                'unicode-range',
     5596                );
     5597
     5598                foreach ( $webfont as $prop => $value ) {
     5599                        if ( ! in_array( $prop, $valid_props, true ) ) {
     5600                                unset( $webfont[ $prop ] );
     5601                        }
     5602                }
     5603
     5604                return $webfont;
     5605        };
     5606
     5607        /**
     5608         * Registers webfonts declared in theme.json.
     5609         *
     5610         * @since 6.0.0
     5611         *
     5612         * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference).
     5613         * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json.
     5614         * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case.
     5615         * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json.
     5616         */
     5617        $fn_register_webfonts = static function() use ( &$registered_webfonts, $fn_get_webfonts_from_theme_json, $fn_convert_keys_to_kebab_case, $fn_validate_webfont, $fn_transform_src_into_uri ) {
     5618                $registered_webfonts = array();
     5619
     5620                foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) {
     5621                        if ( ! is_array( $webfont ) ) {
     5622                                continue;
     5623                        }
     5624
     5625                        $webfont = $fn_convert_keys_to_kebab_case( $webfont );
     5626
     5627                        $webfont = $fn_validate_webfont( $webfont );
     5628
     5629                        $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] );
     5630
     5631                        // Skip if not valid.
     5632                        if ( empty( $webfont ) ) {
     5633                                continue;
     5634                        }
     5635
     5636                        $registered_webfonts[] = $webfont;
     5637                }
     5638        };
     5639
     5640        /**
     5641         * Orders 'src' items to optimize for browser support.
     5642         *
     5643         * @since 6.0.0
     5644         *
     5645         * @param array $webfont Webfont to process.
     5646         * @return array Ordered `src` items.
     5647         */
     5648        $fn_order_src = static function( array $webfont ) {
     5649                $src         = array();
     5650                $src_ordered = array();
     5651
     5652                foreach ( $webfont['src'] as $url ) {
     5653                        // Add data URIs first.
     5654                        if ( str_starts_with( trim( $url ), 'data:' ) ) {
     5655                                $src_ordered[] = array(
     5656                                                'url'    => $url,
     5657                                                'format' => 'data',
     5658                                );
     5659                                continue;
     5660                        }
     5661                        $format         = pathinfo( $url, PATHINFO_EXTENSION );
     5662                        $src[ $format ] = $url;
     5663                }
     5664
     5665                // Add woff2.
     5666                if ( ! empty( $src['woff2'] ) ) {
     5667                        $src_ordered[] = array(
     5668                                        'url'    => sanitize_url( $src['woff2'] ),
     5669                                        'format' => 'woff2',
     5670                        );
     5671                }
     5672
     5673                // Add woff.
     5674                if ( ! empty( $src['woff'] ) ) {
     5675                        $src_ordered[] = array(
     5676                                        'url'    => sanitize_url( $src['woff'] ),
     5677                                        'format' => 'woff',
     5678                        );
     5679                }
     5680
     5681                // Add ttf.
     5682                if ( ! empty( $src['ttf'] ) ) {
     5683                        $src_ordered[] = array(
     5684                                        'url'    => sanitize_url( $src['ttf'] ),
     5685                                        'format' => 'truetype',
     5686                        );
     5687                }
     5688
     5689                // Add eot.
     5690                if ( ! empty( $src['eot'] ) ) {
     5691                        $src_ordered[] = array(
     5692                                        'url'    => sanitize_url( $src['eot'] ),
     5693                                        'format' => 'embedded-opentype',
     5694                        );
     5695                }
     5696
     5697                // Add otf.
     5698                if ( ! empty( $src['otf'] ) ) {
     5699                        $src_ordered[] = array(
     5700                                        'url'    => sanitize_url( $src['otf'] ),
     5701                                        'format' => 'opentype',
     5702                        );
     5703                }
     5704                $webfont['src'] = $src_ordered;
     5705
     5706                return $webfont;
     5707        };
     5708
     5709        /**
     5710         * Compiles the 'src' into valid CSS.
     5711         *
     5712         * @since 6.0.0
     5713         * @since 6.2.0 Removed local() CSS.
     5714         *
     5715         * @param string $font_family Font family.
     5716         * @param array  $value       Value to process.
     5717         * @return string The CSS.
     5718         */
     5719        $fn_compile_src = static function( $font_family, array $value ) {
     5720                $src = '';
     5721
     5722                foreach ( $value as $item ) {
     5723                        $src .= ( 'data' === $item['format'] )
     5724                                        ? ", url({$item['url']})"
     5725                                        : ", url('{$item['url']}') format('{$item['format']}')";
     5726                }
     5727
     5728                $src = ltrim( $src, ', ' );
     5729
     5730                return $src;
     5731        };
     5732
     5733        /**
     5734         * Compiles the font variation settings.
     5735         *
     5736         * @since 6.0.0
     5737         *
     5738         * @param array $font_variation_settings Array of font variation settings.
     5739         * @return string The CSS.
     5740         */
     5741        $fn_compile_variations = static function( array $font_variation_settings ) {
     5742                $variations = '';
     5743
     5744                foreach ( $font_variation_settings as $key => $value ) {
     5745                        $variations .= "$key $value";
     5746                }
     5747
     5748                return $variations;
     5749        };
     5750
     5751        /**
     5752         * Builds the font-family's CSS.
     5753         *
     5754         * @since 6.0.0
     5755         *
     5756         * @uses $fn_compile_src To run the function that compiles the src.
     5757         * @uses $fn_compile_variations To run the function that compiles the variations.
     5758         *
     5759         * @param array $webfont Webfont to process.
     5760         * @return string This font-family's CSS.
     5761         */
     5762        $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) {
     5763                $css = '';
     5764
     5765                // Wrap font-family in quotes if it contains spaces.
     5766                if (
     5767                                str_contains( $webfont['font-family'], ' ' ) &&
     5768                                ! str_contains( $webfont['font-family'], '"' ) &&
     5769                                ! str_contains( $webfont['font-family'], "'" )
     5770                ) {
     5771                        $webfont['font-family'] = '"' . $webfont['font-family'] . '"';
     5772                }
     5773
     5774                foreach ( $webfont as $key => $value ) {
     5775                        /*
     5776                         * Skip "provider", since it's for internal API use,
     5777                         * and not a valid CSS property.
     5778                         */
     5779                        if ( 'provider' === $key ) {
     5780                                continue;
     5781                        }
     5782
     5783                        // Compile the "src" parameter.
     5784                        if ( 'src' === $key ) {
     5785                                $value = $fn_compile_src( $webfont['font-family'], $value );
     5786                        }
     5787
     5788                        // If font-variation-settings is an array, convert it to a string.
     5789                        if ( 'font-variation-settings' === $key && is_array( $value ) ) {
     5790                                $value = $fn_compile_variations( $value );
     5791                        }
     5792
     5793                        if ( ! empty( $value ) ) {
     5794                                $css .= "$key:$value;";
     5795                        }
     5796                }
     5797
     5798                return $css;
     5799        };
     5800
     5801        /**
     5802         * Gets the '@font-face' CSS styles for locally-hosted font files.
     5803         *
     5804         * @since 6.0.0
     5805         *
     5806         * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference).
     5807         * @uses $fn_order_src To run the function that orders the src.
     5808         * @uses $fn_build_font_face_css To run the function that builds the font-face CSS.
     5809         *
     5810         * @return string The `@font-face` CSS.
     5811         */
     5812        $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) {
     5813                $css = '';
     5814
     5815                foreach ( $registered_webfonts as $webfont ) {
     5816                        // Order the webfont's `src` items to optimize for browser support.
     5817                        $webfont = $fn_order_src( $webfont );
     5818
     5819                        // Build the @font-face CSS for this webfont.
     5820                        $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}';
     5821                }
     5822
     5823                return $css;
     5824        };
     5825
     5826        /**
     5827         * Generates and enqueues webfonts styles.
     5828         *
     5829         * @since 6.0.0
     5830         *
     5831         * @uses $fn_get_css To run the function that gets the CSS.
     5832         */
     5833        $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) {
     5834                // Generate the styles.
     5835                $styles = $fn_get_css();
     5836
     5837                // Bail out if there are no styles to enqueue.
     5838                if ( '' === $styles ) {
     5839                        return;
     5840                }
     5841
     5842                // Enqueue the stylesheet.
     5843                wp_register_style( 'wp-webfonts', '' );
     5844                wp_enqueue_style( 'wp-webfonts' );
     5845
     5846                // Add the styles to the stylesheet.
     5847                wp_add_inline_style( 'wp-webfonts', $styles );
     5848        };
     5849
     5850        /**
     5851         * Generates and enqueues editor styles.
     5852         *
     5853         * @since 6.0.0
     5854         *
     5855         * @uses $fn_get_css To run the function that gets the CSS.
     5856         */
     5857        $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) {
     5858                // Generate the styles.
     5859                $styles = $fn_get_css();
     5860
     5861                // Bail out if there are no styles to enqueue.
     5862                if ( '' === $styles ) {
     5863                        return;
     5864                }
     5865
     5866                wp_add_inline_style( 'wp-block-library', $styles );
     5867        };
     5868
     5869        add_action( 'wp_loaded', $fn_register_webfonts );
     5870        add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles );
     5871        add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles );
     5872}
  • trunk/src/wp-includes/script-loader.php

    r56496 r56500  
    32253225
    32263226/**
    3227  * Runs the theme.json webfonts handler.
    3228  *
    3229  * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined
    3230  * in the `theme.json` for the current selection and style
    3231  * variations, validates the font-face properties, generates
    3232  * the '@font-face' style declarations, and then enqueues the
    3233  * styles for both the editor and front-end.
    3234  *
    3235  * Design Notes:
    3236  * This is not a public API, but rather an internal handler.
    3237  * A future public Webfonts API will replace this stopgap code.
    3238  *
    3239  * This code design is intentional.
    3240  *    a. It hides the inner-workings.
    3241  *    b. It does not expose API ins or outs for consumption.
    3242  *    c. It only works with a theme's `theme.json`.
    3243  *
    3244  * Why?
    3245  *    a. To avoid backwards-compatibility issues when
    3246  *       the Webfonts API is introduced in Core.
    3247  *    b. To make `fontFace` declarations in `theme.json` work.
    3248  *
    3249  * @link  https://github.com/WordPress/gutenberg/issues/40472
    3250  *
    3251  * @since 6.0.0
    3252  * @access private
    3253  */
    3254 function _wp_theme_json_webfonts_handler() {
    3255         // Block themes are unavailable during installation.
    3256         if ( wp_installing() ) {
    3257                 return;
    3258         }
    3259 
    3260         if ( ! wp_theme_has_theme_json() ) {
    3261                 return;
    3262         }
    3263 
    3264         // Webfonts to be processed.
    3265         $registered_webfonts = array();
    3266 
    3267         /**
    3268          * Gets the webfonts from theme.json.
    3269          *
    3270          * @since 6.0.0
    3271          *
    3272          * @return array Array of defined webfonts.
    3273          */
    3274         $fn_get_webfonts_from_theme_json = static function() {
    3275                 // Get settings from theme.json.
    3276                 $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings();
    3277 
    3278                 // If in the editor, add webfonts defined in variations.
    3279                 if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
    3280                         $variations = WP_Theme_JSON_Resolver::get_style_variations();
    3281                         foreach ( $variations as $variation ) {
    3282                                 // Skip if fontFamilies are not defined in the variation.
    3283                                 if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) {
    3284                                         continue;
    3285                                 }
    3286 
    3287                                 // Initialize the array structure.
    3288                                 if ( empty( $settings['typography'] ) ) {
    3289                                         $settings['typography'] = array();
    3290                                 }
    3291                                 if ( empty( $settings['typography']['fontFamilies'] ) ) {
    3292                                         $settings['typography']['fontFamilies'] = array();
    3293                                 }
    3294                                 if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) {
    3295                                         $settings['typography']['fontFamilies']['theme'] = array();
    3296                                 }
    3297 
    3298                                 // Combine variations with settings. Remove duplicates.
    3299                                 $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] );
    3300                                 $settings['typography']['fontFamilies']          = array_unique( $settings['typography']['fontFamilies'] );
    3301                         }
    3302                 }
    3303 
    3304                 // Bail out early if there are no settings for webfonts.
    3305                 if ( empty( $settings['typography']['fontFamilies'] ) ) {
    3306                         return array();
    3307                 }
    3308 
    3309                 $webfonts = array();
    3310 
    3311                 // Look for fontFamilies.
    3312                 foreach ( $settings['typography']['fontFamilies'] as $font_families ) {
    3313                         foreach ( $font_families as $font_family ) {
    3314 
    3315                                 // Skip if fontFace is not defined.
    3316                                 if ( empty( $font_family['fontFace'] ) ) {
    3317                                         continue;
    3318                                 }
    3319 
    3320                                 // Skip if fontFace is not an array of webfonts.
    3321                                 if ( ! is_array( $font_family['fontFace'] ) ) {
    3322                                         continue;
    3323                                 }
    3324 
    3325                                 $webfonts = array_merge( $webfonts, $font_family['fontFace'] );
    3326                         }
    3327                 }
    3328 
    3329                 return $webfonts;
    3330         };
    3331 
    3332         /**
    3333          * Transforms each 'src' into an URI by replacing 'file:./'
    3334          * placeholder from theme.json.
    3335          *
    3336          * The absolute path to the webfont file(s) cannot be defined in
    3337          * theme.json. `file:./` is the placeholder which is replaced by
    3338          * the theme's URL path to the theme's root.
    3339          *
    3340          * @since 6.0.0
    3341          *
    3342          * @param array $src Webfont file(s) `src`.
    3343          * @return array Webfont's `src` in URI.
    3344          */
    3345         $fn_transform_src_into_uri = static function( array $src ) {
    3346                 foreach ( $src as $key => $url ) {
    3347                         // Tweak the URL to be relative to the theme root.
    3348                         if ( ! str_starts_with( $url, 'file:./' ) ) {
    3349                                 continue;
    3350                         }
    3351 
    3352                         $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) );
    3353                 }
    3354 
    3355                 return $src;
    3356         };
    3357 
    3358         /**
    3359          * Converts the font-face properties (i.e. keys) into kebab-case.
    3360          *
    3361          * @since 6.0.0
    3362          *
    3363          * @param array $font_face Font face to convert.
    3364          * @return array Font faces with each property in kebab-case format.
    3365          */
    3366         $fn_convert_keys_to_kebab_case = static function( array $font_face ) {
    3367                 foreach ( $font_face as $property => $value ) {
    3368                         $kebab_case               = _wp_to_kebab_case( $property );
    3369                         $font_face[ $kebab_case ] = $value;
    3370                         if ( $kebab_case !== $property ) {
    3371                                 unset( $font_face[ $property ] );
    3372                         }
    3373                 }
    3374 
    3375                 return $font_face;
    3376         };
    3377 
    3378         /**
    3379          * Validates a webfont.
    3380          *
    3381          * @since 6.0.0
    3382          *
    3383          * @param array $webfont The webfont arguments.
    3384          * @return array|false The validated webfont arguments, or false if the webfont is invalid.
    3385          */
    3386         $fn_validate_webfont = static function( $webfont ) {
    3387                 $webfont = wp_parse_args(
    3388                         $webfont,
    3389                         array(
    3390                                 'font-family'  => '',
    3391                                 'font-style'   => 'normal',
    3392                                 'font-weight'  => '400',
    3393                                 'font-display' => 'fallback',
    3394                                 'src'          => array(),
    3395                         )
    3396                 );
    3397 
    3398                 // Check the font-family.
    3399                 if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) {
    3400                         trigger_error( __( 'Webfont font family must be a non-empty string.' ) );
    3401 
    3402                         return false;
    3403                 }
    3404 
    3405                 // Check that the `src` property is defined and a valid type.
    3406                 if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) {
    3407                         trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) );
    3408 
    3409                         return false;
    3410                 }
    3411 
    3412                 // Validate the `src` property.
    3413                 foreach ( (array) $webfont['src'] as $src ) {
    3414                         if ( ! is_string( $src ) || '' === trim( $src ) ) {
    3415                                 trigger_error( __( 'Each webfont src must be a non-empty string.' ) );
    3416 
    3417                                 return false;
    3418                         }
    3419                 }
    3420 
    3421                 // Check the font-weight.
    3422                 if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) {
    3423                         trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) );
    3424 
    3425                         return false;
    3426                 }
    3427 
    3428                 // Check the font-display.
    3429                 if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'optional', 'swap' ), true ) ) {
    3430                         $webfont['font-display'] = 'fallback';
    3431                 }
    3432 
    3433                 $valid_props = array(
    3434                         'ascend-override',
    3435                         'descend-override',
    3436                         'font-display',
    3437                         'font-family',
    3438                         'font-stretch',
    3439                         'font-style',
    3440                         'font-weight',
    3441                         'font-variant',
    3442                         'font-feature-settings',
    3443                         'font-variation-settings',
    3444                         'line-gap-override',
    3445                         'size-adjust',
    3446                         'src',
    3447                         'unicode-range',
    3448                 );
    3449 
    3450                 foreach ( $webfont as $prop => $value ) {
    3451                         if ( ! in_array( $prop, $valid_props, true ) ) {
    3452                                 unset( $webfont[ $prop ] );
    3453                         }
    3454                 }
    3455 
    3456                 return $webfont;
    3457         };
    3458 
    3459         /**
    3460          * Registers webfonts declared in theme.json.
    3461          *
    3462          * @since 6.0.0
    3463          *
    3464          * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference).
    3465          * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json.
    3466          * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case.
    3467          * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json.
    3468          */
    3469         $fn_register_webfonts = static function() use ( &$registered_webfonts, $fn_get_webfonts_from_theme_json, $fn_convert_keys_to_kebab_case, $fn_validate_webfont, $fn_transform_src_into_uri ) {
    3470                 $registered_webfonts = array();
    3471 
    3472                 foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) {
    3473                         if ( ! is_array( $webfont ) ) {
    3474                                 continue;
    3475                         }
    3476 
    3477                         $webfont = $fn_convert_keys_to_kebab_case( $webfont );
    3478 
    3479                         $webfont = $fn_validate_webfont( $webfont );
    3480 
    3481                         $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] );
    3482 
    3483                         // Skip if not valid.
    3484                         if ( empty( $webfont ) ) {
    3485                                 continue;
    3486                         }
    3487 
    3488                         $registered_webfonts[] = $webfont;
    3489                 }
    3490         };
    3491 
    3492         /**
    3493          * Orders 'src' items to optimize for browser support.
    3494          *
    3495          * @since 6.0.0
    3496          *
    3497          * @param array $webfont Webfont to process.
    3498          * @return array Ordered `src` items.
    3499          */
    3500         $fn_order_src = static function( array $webfont ) {
    3501                 $src         = array();
    3502                 $src_ordered = array();
    3503 
    3504                 foreach ( $webfont['src'] as $url ) {
    3505                         // Add data URIs first.
    3506                         if ( str_starts_with( trim( $url ), 'data:' ) ) {
    3507                                 $src_ordered[] = array(
    3508                                         'url'    => $url,
    3509                                         'format' => 'data',
    3510                                 );
    3511                                 continue;
    3512                         }
    3513                         $format         = pathinfo( $url, PATHINFO_EXTENSION );
    3514                         $src[ $format ] = $url;
    3515                 }
    3516 
    3517                 // Add woff2.
    3518                 if ( ! empty( $src['woff2'] ) ) {
    3519                         $src_ordered[] = array(
    3520                                 'url'    => sanitize_url( $src['woff2'] ),
    3521                                 'format' => 'woff2',
    3522                         );
    3523                 }
    3524 
    3525                 // Add woff.
    3526                 if ( ! empty( $src['woff'] ) ) {
    3527                         $src_ordered[] = array(
    3528                                 'url'    => sanitize_url( $src['woff'] ),
    3529                                 'format' => 'woff',
    3530                         );
    3531                 }
    3532 
    3533                 // Add ttf.
    3534                 if ( ! empty( $src['ttf'] ) ) {
    3535                         $src_ordered[] = array(
    3536                                 'url'    => sanitize_url( $src['ttf'] ),
    3537                                 'format' => 'truetype',
    3538                         );
    3539                 }
    3540 
    3541                 // Add eot.
    3542                 if ( ! empty( $src['eot'] ) ) {
    3543                         $src_ordered[] = array(
    3544                                 'url'    => sanitize_url( $src['eot'] ),
    3545                                 'format' => 'embedded-opentype',
    3546                         );
    3547                 }
    3548 
    3549                 // Add otf.
    3550                 if ( ! empty( $src['otf'] ) ) {
    3551                         $src_ordered[] = array(
    3552                                 'url'    => sanitize_url( $src['otf'] ),
    3553                                 'format' => 'opentype',
    3554                         );
    3555                 }
    3556                 $webfont['src'] = $src_ordered;
    3557 
    3558                 return $webfont;
    3559         };
    3560 
    3561         /**
    3562          * Compiles the 'src' into valid CSS.
    3563          *
    3564          * @since 6.0.0
    3565          * @since 6.2.0 Removed local() CSS.
    3566          *
    3567          * @param string $font_family Font family.
    3568          * @param array  $value       Value to process.
    3569          * @return string The CSS.
    3570          */
    3571         $fn_compile_src = static function( $font_family, array $value ) {
    3572                 $src = '';
    3573 
    3574                 foreach ( $value as $item ) {
    3575                         $src .= ( 'data' === $item['format'] )
    3576                                 ? ", url({$item['url']})"
    3577                                 : ", url('{$item['url']}') format('{$item['format']}')";
    3578                 }
    3579 
    3580                 $src = ltrim( $src, ', ' );
    3581 
    3582                 return $src;
    3583         };
    3584 
    3585         /**
    3586          * Compiles the font variation settings.
    3587          *
    3588          * @since 6.0.0
    3589          *
    3590          * @param array $font_variation_settings Array of font variation settings.
    3591          * @return string The CSS.
    3592          */
    3593         $fn_compile_variations = static function( array $font_variation_settings ) {
    3594                 $variations = '';
    3595 
    3596                 foreach ( $font_variation_settings as $key => $value ) {
    3597                         $variations .= "$key $value";
    3598                 }
    3599 
    3600                 return $variations;
    3601         };
    3602 
    3603         /**
    3604          * Builds the font-family's CSS.
    3605          *
    3606          * @since 6.0.0
    3607          *
    3608          * @uses $fn_compile_src To run the function that compiles the src.
    3609          * @uses $fn_compile_variations To run the function that compiles the variations.
    3610          *
    3611          * @param array $webfont Webfont to process.
    3612          * @return string This font-family's CSS.
    3613          */
    3614         $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) {
    3615                 $css = '';
    3616 
    3617                 // Wrap font-family in quotes if it contains spaces.
    3618                 if (
    3619                         str_contains( $webfont['font-family'], ' ' ) &&
    3620                         ! str_contains( $webfont['font-family'], '"' ) &&
    3621                         ! str_contains( $webfont['font-family'], "'" )
    3622                 ) {
    3623                         $webfont['font-family'] = '"' . $webfont['font-family'] . '"';
    3624                 }
    3625 
    3626                 foreach ( $webfont as $key => $value ) {
    3627                         /*
    3628                          * Skip "provider", since it's for internal API use,
    3629                          * and not a valid CSS property.
    3630                          */
    3631                         if ( 'provider' === $key ) {
    3632                                 continue;
    3633                         }
    3634 
    3635                         // Compile the "src" parameter.
    3636                         if ( 'src' === $key ) {
    3637                                 $value = $fn_compile_src( $webfont['font-family'], $value );
    3638                         }
    3639 
    3640                         // If font-variation-settings is an array, convert it to a string.
    3641                         if ( 'font-variation-settings' === $key && is_array( $value ) ) {
    3642                                 $value = $fn_compile_variations( $value );
    3643                         }
    3644 
    3645                         if ( ! empty( $value ) ) {
    3646                                 $css .= "$key:$value;";
    3647                         }
    3648                 }
    3649 
    3650                 return $css;
    3651         };
    3652 
    3653         /**
    3654          * Gets the '@font-face' CSS styles for locally-hosted font files.
    3655          *
    3656          * @since 6.0.0
    3657          *
    3658          * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference).
    3659          * @uses $fn_order_src To run the function that orders the src.
    3660          * @uses $fn_build_font_face_css To run the function that builds the font-face CSS.
    3661          *
    3662          * @return string The `@font-face` CSS.
    3663          */
    3664         $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) {
    3665                 $css = '';
    3666 
    3667                 foreach ( $registered_webfonts as $webfont ) {
    3668                         // Order the webfont's `src` items to optimize for browser support.
    3669                         $webfont = $fn_order_src( $webfont );
    3670 
    3671                         // Build the @font-face CSS for this webfont.
    3672                         $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}';
    3673                 }
    3674 
    3675                 return $css;
    3676         };
    3677 
    3678         /**
    3679          * Generates and enqueues webfonts styles.
    3680          *
    3681          * @since 6.0.0
    3682          *
    3683          * @uses $fn_get_css To run the function that gets the CSS.
    3684          */
    3685         $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) {
    3686                 // Generate the styles.
    3687                 $styles = $fn_get_css();
    3688 
    3689                 // Bail out if there are no styles to enqueue.
    3690                 if ( '' === $styles ) {
    3691                         return;
    3692                 }
    3693 
    3694                 // Enqueue the stylesheet.
    3695                 wp_register_style( 'wp-webfonts', '' );
    3696                 wp_enqueue_style( 'wp-webfonts' );
    3697 
    3698                 // Add the styles to the stylesheet.
    3699                 wp_add_inline_style( 'wp-webfonts', $styles );
    3700         };
    3701 
    3702         /**
    3703          * Generates and enqueues editor styles.
    3704          *
    3705          * @since 6.0.0
    3706          *
    3707          * @uses $fn_get_css To run the function that gets the CSS.
    3708          */
    3709         $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) {
    3710                 // Generate the styles.
    3711                 $styles = $fn_get_css();
    3712 
    3713                 // Bail out if there are no styles to enqueue.
    3714                 if ( '' === $styles ) {
    3715                         return;
    3716                 }
    3717 
    3718                 wp_add_inline_style( 'wp-block-library', $styles );
    3719         };
    3720 
    3721         add_action( 'wp_loaded', $fn_register_webfonts );
    3722         add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles );
    3723         add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles );
    3724 }
    3725 
    3726 /**
    37273227 * Loads classic theme styles on classic themes in the frontend.
    37283228 *
  • trunk/src/wp-settings.php

    r56434 r56500  
    361361require ABSPATH . WPINC . '/style-engine/class-wp-style-engine-css-rules-store.php';
    362362require ABSPATH . WPINC . '/style-engine/class-wp-style-engine-processor.php';
     363require ABSPATH . WPINC . '/fonts/class-wp-font-face-resolver.php';
     364require ABSPATH . WPINC . '/fonts/class-wp-font-face.php';
     365require ABSPATH . WPINC . '/fonts.php';
    363366
    364367$GLOBALS['wp_embed'] = new WP_Embed();
  • trunk/tests/phpunit/tests/theme/themeDir.php

    r55294 r56500  
    186186                        'Block Theme [1.0.0] in subdirectory',
    187187                        'Block Theme Deprecated Path',
    188                         'Webfonts theme',
     188                        'Block Theme with defined Typography Fonts',
    189189                        'Empty `fontFace` in theme.json - no webfonts defined',
    190190                        'A theme with the Update URI header',
Note: See TracChangeset for help on using the changeset viewer.