From 5fa6ac0a062b0152bfaa1e0700a4ad6938030b9f Mon Sep 17 00:00:00 2001 From: hellofromtonya Date: Tue, 22 Aug 2023 11:58:06 -0500 Subject: [PATCH 01/15] Add Font Face and its tests --- phpcs.xml.dist | 1 + src/wp-admin/includes/admin-filters.php | 3 + src/wp-includes/block-editor.php | 1 + src/wp-includes/default-filters.php | 3 + .../font-face/class-wp-font-face-resolver.php | 154 +++++++ .../fonts/font-face/class-wp-font-face.php | 414 ++++++++++++++++++ src/wp-includes/fonts/fonts.php | 39 ++ src/wp-settings.php | 3 + .../fonts/dm-sans/DMSans-Bold-Italic.woff2 | Bin 0 -> 19200 bytes .../assets/fonts/dm-sans/DMSans-Bold.woff2 | Bin 0 -> 18212 bytes .../assets/fonts/dm-sans/DMSans-Medium.ttf | Bin 0 -> 63880 bytes .../fonts/dm-sans/DMSans-MediumItalic.ttf | Bin 0 -> 65764 bytes .../fonts/dm-sans/DMSans-Regular-Italic.woff2 | Bin 0 -> 19004 bytes .../assets/fonts/dm-sans/DMSans-Regular.woff2 | Bin 0 -> 18096 bytes .../assets/fonts/dm-sans/LICENSE.txt | 94 ++++ .../assets/fonts/open-sans/OFL.txt | 93 ++++ ...OpenSans-Italic-VariableFont_wdth,wght.ttf | Bin 0 -> 553648 bytes .../OpenSans-VariableFont_wdth,wght.ttf | Bin 0 -> 505424 bytes .../assets/fonts/source-serif-pro/LICENSE.md | 93 ++++ .../SourceSerif4Variable-Italic.otf.woff2 | Bin 0 -> 280296 bytes .../SourceSerif4Variable-Italic.ttf.woff2 | Bin 0 -> 346840 bytes .../SourceSerif4Variable-Roman.otf.woff2 | Bin 0 -> 348988 bytes .../SourceSerif4Variable-Roman.ttf.woff2 | Bin 0 -> 428448 bytes .../themedir1/fonts-block-theme/style.css | 7 + .../styles/variation-duplicate-fonts.json | 40 ++ .../styles/variation-new-font-family.json | 40 ++ .../styles/variation-new-font-variations.json | 40 ++ .../styles/variation-no-fonts.json | 9 + .../fonts-block-theme/templates/index.html | 3 + .../themedir1/fonts-block-theme/theme.json | 112 +++++ tests/phpunit/tests/fonts/font-face/base.php | 122 ++++++ .../font-face/wp-font-face-tests-dataset.php | 274 ++++++++++++ .../font-face/wpFontFace/generateAndPrint.php | 40 ++ .../getFontsFromThemeJson.php | 99 +++++ .../fonts/font-face/wpPrintFontFaces.php | 58 +++ tests/phpunit/tests/theme/themeDir.php | 1 + 36 files changed, 1743 insertions(+) create mode 100644 src/wp-includes/fonts/font-face/class-wp-font-face-resolver.php create mode 100644 src/wp-includes/fonts/font-face/class-wp-font-face.php create mode 100644 src/wp-includes/fonts/fonts.php create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/DMSans-Bold-Italic.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/DMSans-Bold.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/DMSans-Medium.ttf create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/DMSans-MediumItalic.ttf create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/DMSans-Regular-Italic.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/DMSans-Regular.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/dm-sans/LICENSE.txt create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/open-sans/OFL.txt create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/open-sans/OpenSans-Italic-VariableFont_wdth,wght.ttf create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/open-sans/OpenSans-VariableFont_wdth,wght.ttf create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/source-serif-pro/LICENSE.md create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.otf.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.otf.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2 create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/style.css create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/styles/variation-duplicate-fonts.json create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-family.json create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/styles/variation-new-font-variations.json create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/styles/variation-no-fonts.json create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/templates/index.html create mode 100644 tests/phpunit/data/themedir1/fonts-block-theme/theme.json create mode 100644 tests/phpunit/tests/fonts/font-face/base.php create mode 100644 tests/phpunit/tests/fonts/font-face/wp-font-face-tests-dataset.php create mode 100644 tests/phpunit/tests/fonts/font-face/wpFontFace/generateAndPrint.php create mode 100644 tests/phpunit/tests/fonts/font-face/wpFontFaceResolver/getFontsFromThemeJson.php create mode 100644 tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4e48c8c1c749c..b497b16cfcc35 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -259,6 +259,7 @@ + diff --git a/src/wp-admin/includes/admin-filters.php b/src/wp-admin/includes/admin-filters.php index 33354cb073e32..8e3da338c599c 100644 --- a/src/wp-admin/includes/admin-filters.php +++ b/src/wp-admin/includes/admin-filters.php @@ -168,3 +168,6 @@ // Append '(Draft)' to draft page titles in the privacy page dropdown. add_filter( 'list_pages', '_wp_privacy_settings_filter_draft_page_titles', 10, 2 ); + +// Font management. +add_action( 'admin_print_styles', 'wp_print_font_faces', 50 ); diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 015a553f57b90..05bcd4e7c22bc 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -361,6 +361,7 @@ function _wp_get_iframed_editor_assets() { ob_start(); wp_print_styles(); + wp_print_font_faces(); $styles = ob_get_clean(); ob_start(); diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index bf39f29b4dd93..5e1f1142284ea 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -719,4 +719,7 @@ // CPT wp_block custom postmeta field. add_action( 'init', 'wp_create_initial_post_meta' ); +// Font management. +add_action( 'wp_head', 'wp_print_font_faces', 50 ); + unset( $filter, $action ); diff --git a/src/wp-includes/fonts/font-face/class-wp-font-face-resolver.php b/src/wp-includes/fonts/font-face/class-wp-font-face-resolver.php new file mode 100644 index 0000000000000..ef6b0cd680bd5 --- /dev/null +++ b/src/wp-includes/fonts/font-face/class-wp-font-face-resolver.php @@ -0,0 +1,154 @@ +get_settings(); + + // Bail out early if there are no font settings. + if ( empty( $settings['typography'] ) || empty( $settings['typography']['fontFamilies'] ) ) { + return array(); + } + + return static::parse_settings( $settings ); + } + + /** + * Parse theme.json settings to extract font definitions with variations grouped by font-family. + * + * @since 6.4.0 + * + * @param array $settings Font settings to parse. + * @return array Returns an array of fonts, grouped by font-family. + */ + private static function parse_settings( array $settings ) { + $fonts = array(); + + foreach ( $settings['typography']['fontFamilies'] as $font_families ) { + foreach ( $font_families as $definition ) { + + // Skip if font-family "name" is not defined. + if ( empty( $definition['name'] ) ) { + continue; + } + + // Skip if "fontFace" is not defined, meaning there are no variations. + if ( empty( $definition['fontFace'] ) ) { + continue; + } + + $font_family = $definition['name']; + + // Prepare the fonts array structure for this font-family. + if ( ! array_key_exists( $font_family, $fonts ) ) { + $fonts[ $font_family ] = array(); + } + + $fonts[ $font_family ] = static::convert_font_face_properties( $definition['fontFace'], $font_family ); + } + } + + return $fonts; + } + + /** + * Converts font-face properties from theme.json format. + * + * @since 6.4.0 + * + * @param array $font_face_definition The font-face definitions to convert. + * @param string $font_family_property The value to store in the font-face font-family property. + * @return array Converted font-face properties. + */ + private static function convert_font_face_properties( array $font_face_definition, $font_family_property ) { + $converted_font_faces = array(); + + foreach ( $font_face_definition as $font_face ) { + // Add the font-family property to the font-face. + $font_face['font-family'] = $font_family_property; + + // Converts the "file:./" src placeholder into a theme font file URI. + if ( ! empty( $font_face['src'] ) ) { + $font_face['src'] = static::to_theme_file_uri( (array) $font_face['src'] ); + } + + // Convert camelCase properties into kebab-case. + $font_face = static::to_kebab_case( $font_face ); + + $converted_font_faces[] = $font_face; + } + + return $converted_font_faces; + } + + /** + * Converts each 'file:./' placeholder into a URI to the font file in the theme. + * + * The 'file:./' is specified in the theme's `theme.json` as a placeholder to be + * replaced with the URI to the font file's location in the theme. When a "src" + * beings with this placeholder, it is replaced, converting the src into a URI. + * + * @since 6.4.0 + * + * @param array $src An array of font file sources to process. + * @return array An array of font file src URI(s). + */ + private static function to_theme_file_uri( array $src ) { + $placeholder = 'file:./'; + + foreach ( $src as $src_key => $src_url ) { + // Skip if the src doesn't start with the placeholder, as there's nothing to replace. + if ( ! str_starts_with( $src_url, $placeholder ) ) { + continue; + } + + $src_file = str_replace( $placeholder, '', $src_url ); + $src[ $src_key ] = get_theme_file_uri( $src_file ); + } + + return $src; + } + + /** + * Converts all first dimension keys into kebab-case. + * + * @since 6.4.0 + * + * @param array $data The array to process. + * @return array Data with first dimension keys converted into kebab-case. + */ + private static function to_kebab_case( array $data ) { + foreach ( $data as $key => $value ) { + $kebab_case = _wp_to_kebab_case( $key ); + $data[ $kebab_case ] = $value; + if ( $kebab_case !== $key ) { + unset( $data[ $key ] ); + } + } + + return $data; + } +} diff --git a/src/wp-includes/fonts/font-face/class-wp-font-face.php b/src/wp-includes/fonts/font-face/class-wp-font-face.php new file mode 100644 index 0000000000000..7a9af0e14d8d6 --- /dev/null +++ b/src/wp-includes/fonts/font-face/class-wp-font-face.php @@ -0,0 +1,414 @@ + '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ); + + /** + * Valid font-face property names. + * + * @since 6.4.0 + * + * @var string[] + */ + private $valid_font_face_properties = array( + 'ascent-override', + 'descent-override', + 'font-display', + 'font-family', + 'font-stretch', + 'font-style', + 'font-weight', + 'font-variant', + 'font-feature-settings', + 'font-variation-settings', + 'line-gap-override', + 'size-adjust', + 'src', + 'unicode-range', + ); + + /** + * Valid font-display values. + * + * @since 6.4.0 + * + * @var string[] + */ + private $valid_font_display = array( 'auto', 'block', 'fallback', 'swap', 'optional' ); + + /** + * Array of font-face style tag's attribute(s) + * where the key is the attribute name and the + * value is its value. + * + * @since 6.4.0 + * + * @var string[] + */ + private $style_tag_attrs = array(); + + /** + * Creates and initializes an instance of WP_Font_Face. + * + * @since 6.4.0 + */ + public function __construct() { + /** + * Filters the font-face property defaults. + * + * @since 6.4.0 + * + * @param array $defaults { + * An array of required font-face properties and defaults. + * + * @type string $provider The provider ID. Default 'local'. + * @type string $font-family The font-family property. Default empty string. + * @type string $font-style The font-style property. Default 'normal'. + * @type string $font-weight The font-weight property. Default '400'. + * @type string $font-display The font-display property. Default 'fallback'. + * } + */ + $this->font_face_property_defaults = apply_filters( 'wp_font_face_property_defaults', $this->font_face_property_defaults ); + + if ( + function_exists( 'is_admin' ) && ! is_admin() + && + function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'style' ) + ) { + $this->style_tag_attrs = array( 'type' => 'text/css' ); + } + } + + /** + * Generates and prints the `@font-face` styles for the given fonts. + * + * @since 6.4.0 + * + * @param array $fonts The fonts to generate and print @font-face styles. + */ + public function generate_and_print( array $fonts ) { + $fonts = $this->validate_fonts( $fonts ); + + // Bail out if there are no fonts are given to process. + if ( empty( $fonts ) ) { + return; + } + + printf( + $this->get_style_element(), + $this->get_css( $fonts ) + ); + } + + /** + * Validates each of the font-face properties. + * + * @since 6.4.0 + * + * @param array $fonts The fonts to valid. + * @return array Prepared font-faces organized by provider and font-family. + */ + private function validate_fonts( array $fonts ) { + $validated_fonts = array(); + + foreach ( $fonts as $font_faces ) { + foreach ( $font_faces as $font_face ) { + $font_face = $this->validate_font_face_properties( $font_face ); + // Skip if failed validation. + if ( false === $font_face ) { + continue; + } + + $validated_fonts[] = $font_face; + } + } + + return $validated_fonts; + } + + /** + * Validates each font-face property. + * + * @since 6.4.0 + * + * @param array $font_face Font face properties to validate. + * @return false|array Validated font-face on success. Else, false. + */ + private function validate_font_face_properties( array $font_face ) { + $font_face = wp_parse_args( $font_face, $this->font_face_property_defaults ); + + // Check the font-family. + if ( empty( $font_face['font-family'] ) || ! is_string( $font_face['font-family'] ) ) { + trigger_error( 'Font font-family must be a non-empty string.' ); + return false; + } + + // Make sure that local fonts have 'src' defined. + if ( empty( $font_face['src'] ) || ( ! is_string( $font_face['src'] ) && ! is_array( $font_face['src'] ) ) ) { + trigger_error( 'Font src must be a non-empty string or an array of strings.' ); + return false; + } + + // Validate the 'src' property. + if ( ! empty( $font_face['src'] ) ) { + foreach ( (array) $font_face['src'] as $src ) { + if ( empty( $src ) || ! is_string( $src ) ) { + trigger_error( 'Each font src must be a non-empty string.' ); + return false; + } + } + } + + // Check the font-weight. + if ( ! is_string( $font_face['font-weight'] ) && ! is_int( $font_face['font-weight'] ) ) { + trigger_error( 'Font font-weight must be a properly formatted string or integer.' ); + return false; + } + + // Check the font-display. + if ( ! in_array( $font_face['font-display'], $this->valid_font_display, true ) ) { + $font_face['font-display'] = $this->font_face_property_defaults['font-display']; + } + + // Remove invalid properties. + foreach ( $font_face as $prop => $value ) { + if ( ! in_array( $prop, $this->valid_font_face_properties, true ) ) { + unset( $font_face[ $prop ] ); + } + } + + return $font_face; + } + + /** + * Gets the `\n"; + } + + /** + * Gets the defined \n"; + $expected_output = sprintf( $style_element, $expected ); + + $this->expectOutputString( $expected_output ); + $font_face->generate_and_print( $fonts ); + } +} diff --git a/tests/phpunit/tests/fonts/font-face/wpFontFaceResolver/getFontsFromThemeJson.php b/tests/phpunit/tests/fonts/font-face/wpFontFaceResolver/getFontsFromThemeJson.php new file mode 100644 index 0000000000000..54effbdcdc92a --- /dev/null +++ b/tests/phpunit/tests/fonts/font-face/wpFontFaceResolver/getFontsFromThemeJson.php @@ -0,0 +1,99 @@ +assertIsArray( $fonts, 'Should return an array data type' ); + $this->assertEmpty( $fonts, 'Should return an empty array' ); + } + + public function test_should_return_all_fonts_from_theme() { + switch_theme( static::FONTS_THEME ); + + $actual = WP_Font_Face_Resolver::get_fonts_from_theme_json(); + $expected = $this->get_expected_fonts_for_fonts_block_theme( 'fonts' ); + $this->assertSame( $expected, $actual ); + } + + /** + * @dataProvider data_should_replace_src_file_placeholder + * + * @param string $font_name Font's name. + * @param string $font_index Font's index in the $fonts array. + * @param string $expected Expected src. + */ + public function test_should_replace_src_file_placeholder( $font_name, $font_index, $expected ) { + switch_theme( static::FONTS_THEME ); + + $fonts = WP_Font_Face_Resolver::get_fonts_from_theme_json(); + + $actual = $fonts[ $font_name ][ $font_index ]['src'][0]; + $expected = get_stylesheet_directory_uri() . $expected; + + $this->assertStringNotContainsString( 'file:./', $actual, 'Font src should not contain the "file:./" placeholder' ); + $this->assertSame( $expected, $actual, 'Font src should be an URL to its file' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_replace_src_file_placeholder() { + return array( + // Theme's theme.json. + 'DM Sans: 400 normal' => array( + 'font_name' => 'DM Sans', + 'font_index' => 0, + 'expected' => '/assets/fonts/dm-sans/DMSans-Regular.woff2', + ), + 'DM Sans: 400 italic' => array( + 'font_name' => 'DM Sans', + 'font_index' => 1, + 'expected' => '/assets/fonts/dm-sans/DMSans-Regular-Italic.woff2', + ), + 'DM Sans: 700 normal' => array( + 'font_name' => 'DM Sans', + 'font_index' => 2, + 'expected' => '/assets/fonts/dm-sans/DMSans-Bold.woff2', + ), + 'DM Sans: 700 italic' => array( + 'font_name' => 'DM Sans', + 'font_index' => 3, + 'expected' => '/assets/fonts/dm-sans/DMSans-Bold-Italic.woff2', + ), + 'Source Serif Pro: 200-900 normal' => array( + 'font_name' => 'Source Serif Pro', + 'font_index' => 0, + 'expected' => '/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'Source Serif Pro: 200-900 italic' => array( + 'font_name' => 'Source Serif Pro', + 'font_index' => 1, + 'expected' => '/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + ), + ); + } +} diff --git a/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php b/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php new file mode 100644 index 0000000000000..6416afe6e03fb --- /dev/null +++ b/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php @@ -0,0 +1,58 @@ +expectOutputString( '' ); + wp_print_font_faces(); + } + + /** + * @dataProvider data_should_print_given_fonts + * + * @param array $fonts Fonts to process. + * @param string $expected Expected CSS. + */ + public function test_should_print_given_fonts( array $fonts, $expected ) { + $expected_output = $this->get_expected_styles_output( $expected ); + + $this->expectOutputString( $expected_output ); + wp_print_font_faces( $fonts ); + } + + public function test_should_print_fonts_in_merged_data() { + switch_theme( static::FONTS_THEME ); + + $expected = $this->get_expected_fonts_for_fonts_block_theme( 'font_face_styles' ); + $expected_output = $this->get_expected_styles_output( $expected ); + + $this->expectOutputString( $expected_output ); + wp_print_font_faces(); + } + + private function get_expected_styles_output( $styles ) { + $style_element = "\n"; + return sprintf( $style_element, $styles ); + } +} diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index ea721e5a4494c..1447706ebb243 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -185,6 +185,7 @@ public function test_theme_list() { 'Block Theme [0.4.0]', 'Block Theme [1.0.0] in subdirectory', 'Block Theme Deprecated Path', + 'Block Theme with defined Typography Fonts', 'Webfonts theme', 'Empty `fontFace` in theme.json - no webfonts defined', 'A theme with the Update URI header', From f9b020dc16a7931ba14a257972737c19820f8210 Mon Sep 17 00:00:00 2001 From: hellofromtonya Date: Tue, 22 Aug 2023 13:19:00 -0500 Subject: [PATCH 02/15] Deprecates stopgap code --- src/wp-includes/default-filters.php | 1 - src/wp-includes/deprecated.php | 503 ++++++++++++++++++ src/wp-includes/script-loader.php | 500 ----------------- .../themedir1/webfonts-theme/functions.php | 1 - .../data/themedir1/webfonts-theme/index.php | 4 - .../data/themedir1/webfonts-theme/style.css | 7 - .../webfonts-theme/templates/index.html | 3 - .../data/themedir1/webfonts-theme/theme.json | 103 ---- tests/phpunit/tests/theme/themeDir.php | 1 - .../webfonts/wpThemeJsonWebfontsHandler.php | 138 ----- 10 files changed, 503 insertions(+), 758 deletions(-) delete mode 100644 tests/phpunit/data/themedir1/webfonts-theme/functions.php delete mode 100644 tests/phpunit/data/themedir1/webfonts-theme/index.php delete mode 100644 tests/phpunit/data/themedir1/webfonts-theme/style.css delete mode 100644 tests/phpunit/data/themedir1/webfonts-theme/templates/index.html delete mode 100644 tests/phpunit/data/themedir1/webfonts-theme/theme.json delete mode 100644 tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 5e1f1142284ea..c08073326cafc 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -358,7 +358,6 @@ add_action( 'after_switch_theme', '_wp_menus_changed' ); add_action( 'after_switch_theme', '_wp_sidebars_changed' ); add_action( 'wp_print_styles', 'print_emoji_styles' ); -add_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); if ( isset( $_GET['replytocom'] ) ) { add_filter( 'wp_robots', 'wp_robots_no_robots' ); diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 72bf98d8321b8..1d3ddd519e1d9 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -5367,3 +5367,506 @@ function block_core_navigation_submenu_build_css_colors( $context, $attributes, return $colors; } + +/** + * Runs the theme.json webfonts handler. + * + * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined + * in the `theme.json` for the current selection and style + * variations, validates the font-face properties, generates + * the '@font-face' style declarations, and then enqueues the + * styles for both the editor and front-end. + * + * Design Notes: + * This is not a public API, but rather an internal handler. + * A future public Webfonts API will replace this stopgap code. + * + * This code design is intentional. + * a. It hides the inner-workings. + * b. It does not expose API ins or outs for consumption. + * c. It only works with a theme's `theme.json`. + * + * Why? + * a. To avoid backwards-compatibility issues when + * the Webfonts API is introduced in Core. + * b. To make `fontFace` declarations in `theme.json` work. + * + * @link https://github.com/WordPress/gutenberg/issues/40472 + * + * @since 6.0.0 + * @deprecated 6.4.0 Use wp_print_font_faces() instead. + * @access private + */ +function _wp_theme_json_webfonts_handler() { + _deprecated_function( __FUNCTION__, '6.4.0', 'wp_print_font_faces' ); + + // Block themes are unavailable during installation. + if ( wp_installing() ) { + return; + } + + if ( ! wp_theme_has_theme_json() ) { + return; + } + + // Webfonts to be processed. + $registered_webfonts = array(); + + /** + * Gets the webfonts from theme.json. + * + * @since 6.0.0 + * + * @return array Array of defined webfonts. + */ + $fn_get_webfonts_from_theme_json = static function() { + // Get settings from theme.json. + $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings(); + + // If in the editor, add webfonts defined in variations. + if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + $variations = WP_Theme_JSON_Resolver::get_style_variations(); + foreach ( $variations as $variation ) { + // Skip if fontFamilies are not defined in the variation. + if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) { + continue; + } + + // Initialize the array structure. + if ( empty( $settings['typography'] ) ) { + $settings['typography'] = array(); + } + if ( empty( $settings['typography']['fontFamilies'] ) ) { + $settings['typography']['fontFamilies'] = array(); + } + if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) { + $settings['typography']['fontFamilies']['theme'] = array(); + } + + // Combine variations with settings. Remove duplicates. + $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] ); + $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); + } + } + + // Bail out early if there are no settings for webfonts. + if ( empty( $settings['typography']['fontFamilies'] ) ) { + return array(); + } + + $webfonts = array(); + + // Look for fontFamilies. + foreach ( $settings['typography']['fontFamilies'] as $font_families ) { + foreach ( $font_families as $font_family ) { + + // Skip if fontFace is not defined. + if ( empty( $font_family['fontFace'] ) ) { + continue; + } + + // Skip if fontFace is not an array of webfonts. + if ( ! is_array( $font_family['fontFace'] ) ) { + continue; + } + + $webfonts = array_merge( $webfonts, $font_family['fontFace'] ); + } + } + + return $webfonts; + }; + + /** + * Transforms each 'src' into an URI by replacing 'file:./' + * placeholder from theme.json. + * + * The absolute path to the webfont file(s) cannot be defined in + * theme.json. `file:./` is the placeholder which is replaced by + * the theme's URL path to the theme's root. + * + * @since 6.0.0 + * + * @param array $src Webfont file(s) `src`. + * @return array Webfont's `src` in URI. + */ + $fn_transform_src_into_uri = static function( array $src ) { + foreach ( $src as $key => $url ) { + // Tweak the URL to be relative to the theme root. + if ( ! str_starts_with( $url, 'file:./' ) ) { + continue; + } + + $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) ); + } + + return $src; + }; + + /** + * Converts the font-face properties (i.e. keys) into kebab-case. + * + * @since 6.0.0 + * + * @param array $font_face Font face to convert. + * @return array Font faces with each property in kebab-case format. + */ + $fn_convert_keys_to_kebab_case = static function( array $font_face ) { + foreach ( $font_face as $property => $value ) { + $kebab_case = _wp_to_kebab_case( $property ); + $font_face[ $kebab_case ] = $value; + if ( $kebab_case !== $property ) { + unset( $font_face[ $property ] ); + } + } + + return $font_face; + }; + + /** + * Validates a webfont. + * + * @since 6.0.0 + * + * @param array $webfont The webfont arguments. + * @return array|false The validated webfont arguments, or false if the webfont is invalid. + */ + $fn_validate_webfont = static function( $webfont ) { + $webfont = wp_parse_args( + $webfont, + array( + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + 'src' => array(), + ) + ); + + // Check the font-family. + if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) { + trigger_error( __( 'Webfont font family must be a non-empty string.' ) ); + + return false; + } + + // Check that the `src` property is defined and a valid type. + if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) { + trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) ); + + return false; + } + + // Validate the `src` property. + foreach ( (array) $webfont['src'] as $src ) { + if ( ! is_string( $src ) || '' === trim( $src ) ) { + trigger_error( __( 'Each webfont src must be a non-empty string.' ) ); + + return false; + } + } + + // Check the font-weight. + if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) { + trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) ); + + return false; + } + + // Check the font-display. + if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'optional', 'swap' ), true ) ) { + $webfont['font-display'] = 'fallback'; + } + + $valid_props = array( + 'ascend-override', + 'descend-override', + 'font-display', + 'font-family', + 'font-stretch', + 'font-style', + 'font-weight', + 'font-variant', + 'font-feature-settings', + 'font-variation-settings', + 'line-gap-override', + 'size-adjust', + 'src', + 'unicode-range', + ); + + foreach ( $webfont as $prop => $value ) { + if ( ! in_array( $prop, $valid_props, true ) ) { + unset( $webfont[ $prop ] ); + } + } + + return $webfont; + }; + + /** + * Registers webfonts declared in theme.json. + * + * @since 6.0.0 + * + * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). + * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json. + * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case. + * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json. + */ + $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 ) { + $registered_webfonts = array(); + + foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) { + if ( ! is_array( $webfont ) ) { + continue; + } + + $webfont = $fn_convert_keys_to_kebab_case( $webfont ); + + $webfont = $fn_validate_webfont( $webfont ); + + $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] ); + + // Skip if not valid. + if ( empty( $webfont ) ) { + continue; + } + + $registered_webfonts[] = $webfont; + } + }; + + /** + * Orders 'src' items to optimize for browser support. + * + * @since 6.0.0 + * + * @param array $webfont Webfont to process. + * @return array Ordered `src` items. + */ + $fn_order_src = static function( array $webfont ) { + $src = array(); + $src_ordered = array(); + + foreach ( $webfont['src'] as $url ) { + // Add data URIs first. + if ( str_starts_with( trim( $url ), 'data:' ) ) { + $src_ordered[] = array( + 'url' => $url, + 'format' => 'data', + ); + continue; + } + $format = pathinfo( $url, PATHINFO_EXTENSION ); + $src[ $format ] = $url; + } + + // Add woff2. + if ( ! empty( $src['woff2'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['woff2'] ), + 'format' => 'woff2', + ); + } + + // Add woff. + if ( ! empty( $src['woff'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['woff'] ), + 'format' => 'woff', + ); + } + + // Add ttf. + if ( ! empty( $src['ttf'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['ttf'] ), + 'format' => 'truetype', + ); + } + + // Add eot. + if ( ! empty( $src['eot'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['eot'] ), + 'format' => 'embedded-opentype', + ); + } + + // Add otf. + if ( ! empty( $src['otf'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['otf'] ), + 'format' => 'opentype', + ); + } + $webfont['src'] = $src_ordered; + + return $webfont; + }; + + /** + * Compiles the 'src' into valid CSS. + * + * @since 6.0.0 + * @since 6.2.0 Removed local() CSS. + * + * @param string $font_family Font family. + * @param array $value Value to process. + * @return string The CSS. + */ + $fn_compile_src = static function( $font_family, array $value ) { + $src = ''; + + foreach ( $value as $item ) { + $src .= ( 'data' === $item['format'] ) + ? ", url({$item['url']})" + : ", url('{$item['url']}') format('{$item['format']}')"; + } + + $src = ltrim( $src, ', ' ); + + return $src; + }; + + /** + * Compiles the font variation settings. + * + * @since 6.0.0 + * + * @param array $font_variation_settings Array of font variation settings. + * @return string The CSS. + */ + $fn_compile_variations = static function( array $font_variation_settings ) { + $variations = ''; + + foreach ( $font_variation_settings as $key => $value ) { + $variations .= "$key $value"; + } + + return $variations; + }; + + /** + * Builds the font-family's CSS. + * + * @since 6.0.0 + * + * @uses $fn_compile_src To run the function that compiles the src. + * @uses $fn_compile_variations To run the function that compiles the variations. + * + * @param array $webfont Webfont to process. + * @return string This font-family's CSS. + */ + $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) { + $css = ''; + + // Wrap font-family in quotes if it contains spaces. + if ( + str_contains( $webfont['font-family'], ' ' ) && + ! str_contains( $webfont['font-family'], '"' ) && + ! str_contains( $webfont['font-family'], "'" ) + ) { + $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; + } + + foreach ( $webfont as $key => $value ) { + /* + * Skip "provider", since it's for internal API use, + * and not a valid CSS property. + */ + if ( 'provider' === $key ) { + continue; + } + + // Compile the "src" parameter. + if ( 'src' === $key ) { + $value = $fn_compile_src( $webfont['font-family'], $value ); + } + + // If font-variation-settings is an array, convert it to a string. + if ( 'font-variation-settings' === $key && is_array( $value ) ) { + $value = $fn_compile_variations( $value ); + } + + if ( ! empty( $value ) ) { + $css .= "$key:$value;"; + } + } + + return $css; + }; + + /** + * Gets the '@font-face' CSS styles for locally-hosted font files. + * + * @since 6.0.0 + * + * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). + * @uses $fn_order_src To run the function that orders the src. + * @uses $fn_build_font_face_css To run the function that builds the font-face CSS. + * + * @return string The `@font-face` CSS. + */ + $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) { + $css = ''; + + foreach ( $registered_webfonts as $webfont ) { + // Order the webfont's `src` items to optimize for browser support. + $webfont = $fn_order_src( $webfont ); + + // Build the @font-face CSS for this webfont. + $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}'; + } + + return $css; + }; + + /** + * Generates and enqueues webfonts styles. + * + * @since 6.0.0 + * + * @uses $fn_get_css To run the function that gets the CSS. + */ + $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) { + // Generate the styles. + $styles = $fn_get_css(); + + // Bail out if there are no styles to enqueue. + if ( '' === $styles ) { + return; + } + + // Enqueue the stylesheet. + wp_register_style( 'wp-webfonts', '' ); + wp_enqueue_style( 'wp-webfonts' ); + + // Add the styles to the stylesheet. + wp_add_inline_style( 'wp-webfonts', $styles ); + }; + + /** + * Generates and enqueues editor styles. + * + * @since 6.0.0 + * + * @uses $fn_get_css To run the function that gets the CSS. + */ + $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) { + // Generate the styles. + $styles = $fn_get_css(); + + // Bail out if there are no styles to enqueue. + if ( '' === $styles ) { + return; + } + + wp_add_inline_style( 'wp-block-library', $styles ); + }; + + add_action( 'wp_loaded', $fn_register_webfonts ); + add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles ); + add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles ); +} diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 205cedda72580..6a8a11f3b8bb3 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3198,506 +3198,6 @@ function wp_enqueue_block_style( $block_name, $args ) { add_action( 'enqueue_block_assets', $callback ); } -/** - * Runs the theme.json webfonts handler. - * - * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined - * in the `theme.json` for the current selection and style - * variations, validates the font-face properties, generates - * the '@font-face' style declarations, and then enqueues the - * styles for both the editor and front-end. - * - * Design Notes: - * This is not a public API, but rather an internal handler. - * A future public Webfonts API will replace this stopgap code. - * - * This code design is intentional. - * a. It hides the inner-workings. - * b. It does not expose API ins or outs for consumption. - * c. It only works with a theme's `theme.json`. - * - * Why? - * a. To avoid backwards-compatibility issues when - * the Webfonts API is introduced in Core. - * b. To make `fontFace` declarations in `theme.json` work. - * - * @link https://github.com/WordPress/gutenberg/issues/40472 - * - * @since 6.0.0 - * @access private - */ -function _wp_theme_json_webfonts_handler() { - // Block themes are unavailable during installation. - if ( wp_installing() ) { - return; - } - - if ( ! wp_theme_has_theme_json() ) { - return; - } - - // Webfonts to be processed. - $registered_webfonts = array(); - - /** - * Gets the webfonts from theme.json. - * - * @since 6.0.0 - * - * @return array Array of defined webfonts. - */ - $fn_get_webfonts_from_theme_json = static function() { - // Get settings from theme.json. - $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings(); - - // If in the editor, add webfonts defined in variations. - if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { - $variations = WP_Theme_JSON_Resolver::get_style_variations(); - foreach ( $variations as $variation ) { - // Skip if fontFamilies are not defined in the variation. - if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) { - continue; - } - - // Initialize the array structure. - if ( empty( $settings['typography'] ) ) { - $settings['typography'] = array(); - } - if ( empty( $settings['typography']['fontFamilies'] ) ) { - $settings['typography']['fontFamilies'] = array(); - } - if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) { - $settings['typography']['fontFamilies']['theme'] = array(); - } - - // Combine variations with settings. Remove duplicates. - $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] ); - $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); - } - } - - // Bail out early if there are no settings for webfonts. - if ( empty( $settings['typography']['fontFamilies'] ) ) { - return array(); - } - - $webfonts = array(); - - // Look for fontFamilies. - foreach ( $settings['typography']['fontFamilies'] as $font_families ) { - foreach ( $font_families as $font_family ) { - - // Skip if fontFace is not defined. - if ( empty( $font_family['fontFace'] ) ) { - continue; - } - - // Skip if fontFace is not an array of webfonts. - if ( ! is_array( $font_family['fontFace'] ) ) { - continue; - } - - $webfonts = array_merge( $webfonts, $font_family['fontFace'] ); - } - } - - return $webfonts; - }; - - /** - * Transforms each 'src' into an URI by replacing 'file:./' - * placeholder from theme.json. - * - * The absolute path to the webfont file(s) cannot be defined in - * theme.json. `file:./` is the placeholder which is replaced by - * the theme's URL path to the theme's root. - * - * @since 6.0.0 - * - * @param array $src Webfont file(s) `src`. - * @return array Webfont's `src` in URI. - */ - $fn_transform_src_into_uri = static function( array $src ) { - foreach ( $src as $key => $url ) { - // Tweak the URL to be relative to the theme root. - if ( ! str_starts_with( $url, 'file:./' ) ) { - continue; - } - - $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) ); - } - - return $src; - }; - - /** - * Converts the font-face properties (i.e. keys) into kebab-case. - * - * @since 6.0.0 - * - * @param array $font_face Font face to convert. - * @return array Font faces with each property in kebab-case format. - */ - $fn_convert_keys_to_kebab_case = static function( array $font_face ) { - foreach ( $font_face as $property => $value ) { - $kebab_case = _wp_to_kebab_case( $property ); - $font_face[ $kebab_case ] = $value; - if ( $kebab_case !== $property ) { - unset( $font_face[ $property ] ); - } - } - - return $font_face; - }; - - /** - * Validates a webfont. - * - * @since 6.0.0 - * - * @param array $webfont The webfont arguments. - * @return array|false The validated webfont arguments, or false if the webfont is invalid. - */ - $fn_validate_webfont = static function( $webfont ) { - $webfont = wp_parse_args( - $webfont, - array( - 'font-family' => '', - 'font-style' => 'normal', - 'font-weight' => '400', - 'font-display' => 'fallback', - 'src' => array(), - ) - ); - - // Check the font-family. - if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) { - trigger_error( __( 'Webfont font family must be a non-empty string.' ) ); - - return false; - } - - // Check that the `src` property is defined and a valid type. - if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) { - trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) ); - - return false; - } - - // Validate the `src` property. - foreach ( (array) $webfont['src'] as $src ) { - if ( ! is_string( $src ) || '' === trim( $src ) ) { - trigger_error( __( 'Each webfont src must be a non-empty string.' ) ); - - return false; - } - } - - // Check the font-weight. - if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) { - trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) ); - - return false; - } - - // Check the font-display. - if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'optional', 'swap' ), true ) ) { - $webfont['font-display'] = 'fallback'; - } - - $valid_props = array( - 'ascend-override', - 'descend-override', - 'font-display', - 'font-family', - 'font-stretch', - 'font-style', - 'font-weight', - 'font-variant', - 'font-feature-settings', - 'font-variation-settings', - 'line-gap-override', - 'size-adjust', - 'src', - 'unicode-range', - ); - - foreach ( $webfont as $prop => $value ) { - if ( ! in_array( $prop, $valid_props, true ) ) { - unset( $webfont[ $prop ] ); - } - } - - return $webfont; - }; - - /** - * Registers webfonts declared in theme.json. - * - * @since 6.0.0 - * - * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). - * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json. - * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case. - * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json. - */ - $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 ) { - $registered_webfonts = array(); - - foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) { - if ( ! is_array( $webfont ) ) { - continue; - } - - $webfont = $fn_convert_keys_to_kebab_case( $webfont ); - - $webfont = $fn_validate_webfont( $webfont ); - - $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] ); - - // Skip if not valid. - if ( empty( $webfont ) ) { - continue; - } - - $registered_webfonts[] = $webfont; - } - }; - - /** - * Orders 'src' items to optimize for browser support. - * - * @since 6.0.0 - * - * @param array $webfont Webfont to process. - * @return array Ordered `src` items. - */ - $fn_order_src = static function( array $webfont ) { - $src = array(); - $src_ordered = array(); - - foreach ( $webfont['src'] as $url ) { - // Add data URIs first. - if ( str_starts_with( trim( $url ), 'data:' ) ) { - $src_ordered[] = array( - 'url' => $url, - 'format' => 'data', - ); - continue; - } - $format = pathinfo( $url, PATHINFO_EXTENSION ); - $src[ $format ] = $url; - } - - // Add woff2. - if ( ! empty( $src['woff2'] ) ) { - $src_ordered[] = array( - 'url' => sanitize_url( $src['woff2'] ), - 'format' => 'woff2', - ); - } - - // Add woff. - if ( ! empty( $src['woff'] ) ) { - $src_ordered[] = array( - 'url' => sanitize_url( $src['woff'] ), - 'format' => 'woff', - ); - } - - // Add ttf. - if ( ! empty( $src['ttf'] ) ) { - $src_ordered[] = array( - 'url' => sanitize_url( $src['ttf'] ), - 'format' => 'truetype', - ); - } - - // Add eot. - if ( ! empty( $src['eot'] ) ) { - $src_ordered[] = array( - 'url' => sanitize_url( $src['eot'] ), - 'format' => 'embedded-opentype', - ); - } - - // Add otf. - if ( ! empty( $src['otf'] ) ) { - $src_ordered[] = array( - 'url' => sanitize_url( $src['otf'] ), - 'format' => 'opentype', - ); - } - $webfont['src'] = $src_ordered; - - return $webfont; - }; - - /** - * Compiles the 'src' into valid CSS. - * - * @since 6.0.0 - * @since 6.2.0 Removed local() CSS. - * - * @param string $font_family Font family. - * @param array $value Value to process. - * @return string The CSS. - */ - $fn_compile_src = static function( $font_family, array $value ) { - $src = ''; - - foreach ( $value as $item ) { - $src .= ( 'data' === $item['format'] ) - ? ", url({$item['url']})" - : ", url('{$item['url']}') format('{$item['format']}')"; - } - - $src = ltrim( $src, ', ' ); - - return $src; - }; - - /** - * Compiles the font variation settings. - * - * @since 6.0.0 - * - * @param array $font_variation_settings Array of font variation settings. - * @return string The CSS. - */ - $fn_compile_variations = static function( array $font_variation_settings ) { - $variations = ''; - - foreach ( $font_variation_settings as $key => $value ) { - $variations .= "$key $value"; - } - - return $variations; - }; - - /** - * Builds the font-family's CSS. - * - * @since 6.0.0 - * - * @uses $fn_compile_src To run the function that compiles the src. - * @uses $fn_compile_variations To run the function that compiles the variations. - * - * @param array $webfont Webfont to process. - * @return string This font-family's CSS. - */ - $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) { - $css = ''; - - // Wrap font-family in quotes if it contains spaces. - if ( - str_contains( $webfont['font-family'], ' ' ) && - ! str_contains( $webfont['font-family'], '"' ) && - ! str_contains( $webfont['font-family'], "'" ) - ) { - $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; - } - - foreach ( $webfont as $key => $value ) { - /* - * Skip "provider", since it's for internal API use, - * and not a valid CSS property. - */ - if ( 'provider' === $key ) { - continue; - } - - // Compile the "src" parameter. - if ( 'src' === $key ) { - $value = $fn_compile_src( $webfont['font-family'], $value ); - } - - // If font-variation-settings is an array, convert it to a string. - if ( 'font-variation-settings' === $key && is_array( $value ) ) { - $value = $fn_compile_variations( $value ); - } - - if ( ! empty( $value ) ) { - $css .= "$key:$value;"; - } - } - - return $css; - }; - - /** - * Gets the '@font-face' CSS styles for locally-hosted font files. - * - * @since 6.0.0 - * - * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). - * @uses $fn_order_src To run the function that orders the src. - * @uses $fn_build_font_face_css To run the function that builds the font-face CSS. - * - * @return string The `@font-face` CSS. - */ - $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) { - $css = ''; - - foreach ( $registered_webfonts as $webfont ) { - // Order the webfont's `src` items to optimize for browser support. - $webfont = $fn_order_src( $webfont ); - - // Build the @font-face CSS for this webfont. - $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}'; - } - - return $css; - }; - - /** - * Generates and enqueues webfonts styles. - * - * @since 6.0.0 - * - * @uses $fn_get_css To run the function that gets the CSS. - */ - $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) { - // Generate the styles. - $styles = $fn_get_css(); - - // Bail out if there are no styles to enqueue. - if ( '' === $styles ) { - return; - } - - // Enqueue the stylesheet. - wp_register_style( 'wp-webfonts', '' ); - wp_enqueue_style( 'wp-webfonts' ); - - // Add the styles to the stylesheet. - wp_add_inline_style( 'wp-webfonts', $styles ); - }; - - /** - * Generates and enqueues editor styles. - * - * @since 6.0.0 - * - * @uses $fn_get_css To run the function that gets the CSS. - */ - $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) { - // Generate the styles. - $styles = $fn_get_css(); - - // Bail out if there are no styles to enqueue. - if ( '' === $styles ) { - return; - } - - wp_add_inline_style( 'wp-block-library', $styles ); - }; - - add_action( 'wp_loaded', $fn_register_webfonts ); - add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles ); - add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles ); -} - /** * Loads classic theme styles on classic themes in the frontend. * diff --git a/tests/phpunit/data/themedir1/webfonts-theme/functions.php b/tests/phpunit/data/themedir1/webfonts-theme/functions.php deleted file mode 100644 index b3d9bbc7f3711..0000000000000 --- a/tests/phpunit/data/themedir1/webfonts-theme/functions.php +++ /dev/null @@ -1 +0,0 @@ - -

Index Template

- diff --git a/tests/phpunit/data/themedir1/webfonts-theme/theme.json b/tests/phpunit/data/themedir1/webfonts-theme/theme.json deleted file mode 100644 index 0606b9b780971..0000000000000 --- a/tests/phpunit/data/themedir1/webfonts-theme/theme.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "version": 2, - "customTemplates": [ - { - "name": "blank", - "title": "Blank", - "postTypes": [ - "page", - "post" - ] - } - ], - "settings": { - "appearanceTools": true, - "color": { - "duotone": [], - "gradients": [], - "palette": [] - }, - "custom": {}, - "spacing": { - "units": [ - "%", - "px", - "em", - "rem", - "vh", - "vw" - ] - }, - "typography": { - "dropCap": false, - "fontFamilies": [ - { - "fontFamily": "\"Source Serif Pro\", serif", - "name": "Source Serif Pro", - "slug": "source-serif-pro", - "fontFace": [ - { - "fontFamily": "Source Serif Pro", - "fontWeight": "200 900", - "fontStyle": "normal", - "fontStretch": "normal", - "src": [ "file:./assets/fonts/SourceSerif4Variable-Roman.ttf.woff2" ] - }, - { - "fontFamily": "Source Serif Pro", - "fontWeight": "200 900", - "fontStyle": "italic", - "fontStretch": "normal", - "src": [ "file:./assets/fonts/SourceSerif4Variable-Italic.ttf.woff2" ] - } - ] - } - ], - "fontSizes": [ - { - "size": "1rem", - "slug": "small" - }, - { - "size": "1.125rem", - "slug": "medium" - }, - { - "size": "1.75rem", - "slug": "large" - }, - { - "size": "clamp(1.75rem, 3vw, 2.25rem)", - "slug": "x-large" - } - ] - }, - "layout": { - "contentSize": "650px", - "wideSize": "1000px" - } - }, - "styles": { - "blocks": {}, - "color": { - "background": "var(--wp--preset--color--background)", - "text": "var(--wp--preset--color--foreground)" - }, - "elements": {}, - "spacing": { - "blockGap": "1.5rem" - }, - "typography": { - "fontFamily": "var(--wp--preset--font-family--system-font)", - "lineHeight": "var(--wp--custom--typography--line-height--normal)", - "fontSize": "var(--wp--preset--font-size--medium)" - } - }, - "templateParts": [ - { - "name": "header", - "title": "Header", - "area": "header" - } - ] -} diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index 1447706ebb243..73cf63539e58f 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -186,7 +186,6 @@ public function test_theme_list() { 'Block Theme [1.0.0] in subdirectory', 'Block Theme Deprecated Path', 'Block Theme with defined Typography Fonts', - 'Webfonts theme', 'Empty `fontFace` in theme.json - no webfonts defined', 'A theme with the Update URI header', ); diff --git a/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php b/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php deleted file mode 100644 index 7202417ddee93..0000000000000 --- a/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php +++ /dev/null @@ -1,138 +0,0 @@ -orig_wp_styles = $wp_styles; - $wp_styles = null; - - $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); - $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; - - // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. - $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); - - $theme_root_callback = function () { - return $this->theme_root; - }; - - add_filter( 'theme_root', $theme_root_callback ); - add_filter( 'stylesheet_root', $theme_root_callback ); - add_filter( 'template_root', $theme_root_callback ); - - // Clear caches. - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - } - - public function tear_down() { - global $wp_styles; - $wp_styles = $this->orig_wp_styles; - - // Restore the original theme directory setup. - $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - - parent::tear_down(); - } - - /** - * @ticket 55567 - * @ticket 46370 - * @ticket 57430 - */ - public function test_font_face_generated_from_themejson() { - $this->setup_theme_and_test( 'webfonts-theme' ); - - $expected = << -@font-face{font-family:"Source Serif Pro";font-style:normal;font-weight:200 900;font-display:fallback;src:url('THEME_ROOT_URL/assets/fonts/SourceSerif4Variable-Roman.ttf.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"Source Serif Pro";font-style:italic;font-weight:200 900;font-display:fallback;src:url('THEME_ROOT_URL/assets/fonts/SourceSerif4Variable-Italic.ttf.woff2') format('woff2');font-stretch:normal;} - -EOF; - $expected = str_replace( 'THEME_ROOT_URL', get_stylesheet_directory_uri(), $expected ); - $expected = str_replace( "\r\n", "\n", $expected ); - - $this->assertStringContainsString( - $expected, - get_echo( 'wp_print_styles' ) - ); - } - - /** - * @dataProvider data_font_face_not_generated - * - * @ticket 55567 - * @ticket 46370 - */ - public function test_font_face_not_generated( $theme_name ) { - $this->setup_theme_and_test( $theme_name ); - - $actual = get_echo( 'wp_print_styles' ); - $this->assertStringNotContainsString( " and open a + +CSS; $this->expectOutputString( $expected_output ); + wp_print_font_faces( $fonts ); } From c8e4c03546418aae76f62ba1dc07dd2193b847e7 Mon Sep 17 00:00:00 2001 From: hellofromtonya Date: Wed, 30 Aug 2023 18:11:15 -0500 Subject: [PATCH 14/15] Rename properties to declarations --- src/wp-includes/fonts/class-wp-font-face.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/fonts/class-wp-font-face.php b/src/wp-includes/fonts/class-wp-font-face.php index f49e6b1e62a05..974e13ed1db8c 100644 --- a/src/wp-includes/fonts/class-wp-font-face.php +++ b/src/wp-includes/fonts/class-wp-font-face.php @@ -134,7 +134,7 @@ private function validate_fonts( array $fonts ) { foreach ( $fonts as $font_faces ) { foreach ( $font_faces as $font_face ) { - $font_face = $this->validate_font_face_properties( $font_face ); + $font_face = $this->validate_font_face_declarations( $font_face ); // Skip if failed validation. if ( false === $font_face ) { continue; @@ -148,14 +148,14 @@ private function validate_fonts( array $fonts ) { } /** - * Validates each font-face property. + * Validates each font-face declaration (property and value pairing). * * @since 6.4.0 * - * @param array $font_face Font face properties to validate. + * @param array $font_face Font face property and value pairings to validate. * @return array|false Validated font-face on success, or false on failure. */ - private function validate_font_face_properties( array $font_face ) { + private function validate_font_face_declarations( array $font_face ) { $font_face = wp_parse_args( $font_face, $this->font_face_property_defaults ); // Check the font-family. From 0fd304eb2446f1d2d678e27ca4e79d9f218bd01f Mon Sep 17 00:00:00 2001 From: hellofromtonya Date: Wed, 30 Aug 2023 18:24:35 -0500 Subject: [PATCH 15/15] Run tests in separate processes --- src/wp-includes/fonts.php | 7 +------ tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php | 8 +++++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/fonts.php b/src/wp-includes/fonts.php index b51a9bdaa0bde..b628db18cf1e2 100644 --- a/src/wp-includes/fonts.php +++ b/src/wp-includes/fonts.php @@ -49,12 +49,7 @@ function wp_print_font_faces( $fonts = array() ) { return; } - if ( - null === $wp_font_face || - - // Ignore cache when automated test suites are running. - ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) - ) { + if ( null === $wp_font_face ) { $wp_font_face = new WP_Font_Face(); } diff --git a/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php b/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php index 9cc67fb39a6bb..aa8d9b1d2f316 100644 --- a/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php +++ b/tests/phpunit/tests/fonts/font-face/wpPrintFontFaces.php @@ -6,11 +6,17 @@ * @subpackage Fonts * * @since 6.4.0 - * + */ +require_once __DIR__ . '/base.php'; + +/** * @group fonts * @group fontface * * @covers wp_print_font_faces + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled */ class Tests_Fonts_WpPrintFontFaces extends WP_Font_Face_UnitTestCase { const FONTS_THEME = 'fonts-block-theme';