Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fonts API] Refactor theme.json global functions into the WP_Fonts_Resolver #50914

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/class-wp-theme-json-resolver-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ public static function get_theme_data( $deprecated = array(), $options = array()
}

// BEGIN OF EXPERIMENTAL CODE. Not to backport to core.
static::$theme = gutenberg_add_registered_fonts_to_theme_json( static::$theme );
static::$theme = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( static::$theme );
// END OF EXPERIMENTAL CODE.

}
Expand Down
287 changes: 287 additions & 0 deletions lib/experimental/fonts-api/class-wp-fonts-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @access private
*/
class WP_Fonts_Resolver {

/**
* Defines the key structure in global styles to the fontFamily
* user-selected font.
Expand All @@ -37,6 +38,292 @@ class WP_Fonts_Resolver {
array( 'typography', 'fontFamily' ),
);

/**
* Register fonts defined in theme.json.
*
* @since X.X.X
*/
public static function register_fonts_from_theme_json() {
$settings = static::get_settings();

// Bail out early if there are no settings for fonts.
if ( empty( $settings['typography'] ) || empty( $settings['typography']['fontFamilies'] ) ) {
return;
}

list( $fonts, $handles ) = static::parse_font_families( $settings );

wp_register_fonts( $fonts );
wp_enqueue_fonts( $handles );
}

/**
* Add missing fonts to the given global styles data.
*
* @since X.X.X
*
* @param WP_Theme_JSON_Gutenberg $data The global styles.
* @return WP_Theme_JSON_Gutenberg The global styles with missing fonts data.
*/
public static function add_missing_fonts_to_theme_json( $data ) {
$font_families_registered = wp_fonts()->get_registered_font_families();

$raw_data = $data->get_raw_data();

$font_families_from_theme = ! empty( $raw_data['settings']['typography']['fontFamilies']['theme'] )
? $raw_data['settings']['typography']['fontFamilies']['theme']
: array();

// Find missing fonts that are not in the theme's theme.json.
$to_add = array();
if ( ! empty( $font_families_registered ) ) {
$to_add = array_diff( $font_families_registered, static::get_font_families( $font_families_from_theme ) );
}

// Bail out early if there are no missing fonts.
if ( empty( $to_add ) ) {
return $data;
}

/*
* Make sure the path to settings.typography.fontFamilies.theme exists
* before adding missing fonts.
*/
if ( empty( $raw_data['settings'] ) ) {
$raw_data['settings'] = array();
}
$raw_data['settings'] = static::set_tyopgraphy_settings_array_structure( $raw_data['settings'] );

foreach ( $to_add as $font_family_handle ) {
$raw_data['settings']['typography']['fontFamilies']['theme'][] = wp_fonts()->to_theme_json( $font_family_handle );
}

return new WP_Theme_JSON_Gutenberg( $raw_data );
}

/**
* Sets the typography.fontFamilies.theme structure in the given array, if not already set.
* if not already set.
*
* @since X.X.X
*
* @param array $data The target array to process.
* @return array Data array with typography.fontFamilies.theme structure set.
*/
private static function set_tyopgraphy_settings_array_structure( array $data ) {
if ( empty( $data['typography'] ) ) {
$data['typography'] = array();
}
if ( empty( $data['typography']['fontFamilies'] ) ) {
$data['typography']['fontFamilies'] = array();
}
if ( empty( $data['typography']['fontFamilies']['theme'] ) ) {
$data['typography']['fontFamilies']['theme'] = array();
}

return $data;
}

/**
* Returns theme's settings and adds webfonts defined in variations.
*
* @since X.X.X
*
* @return array An array containing theme's settings.
*/
private static function get_settings() {
// Get settings.
$settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->get_settings();

if ( ! is_admin() && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
return $settings;
}

// If in the editor, add fonts defined in variations.
$variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations();

$set_theme_structure = true;
foreach ( $variations as $variation ) {

// Skip if settings.typography.fontFamilies are not defined in the variation.
if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) {
continue;
}

// One time, set any missing parts of the array structure.
if ( $set_theme_structure ) {
$set_theme_structure = false;
$settings = static::set_tyopgraphy_settings_array_structure( $settings );
}

// Merge the variation settings with the global settings.
$settings['typography']['fontFamilies']['theme'] = array_merge(
$settings['typography']['fontFamilies']['theme'],
$variation['settings']['typography']['fontFamilies']['theme']
);

// Remove duplicates.
$settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] );
}

return $settings;
}

/**
* Converts a list of font families into font handles and returns them as an array.
*
* @since X.X.X
*
* @param array $families_data An array of font families data.
* @return array An array containing font handles.
*/
private static function get_font_families( $families_data ) {
$families = array();
foreach ( $families_data as $family ) {
$font_family = WP_Fonts_Utils::get_font_family_from_variation( $family );
$handle = WP_Fonts_Utils::convert_font_family_into_handle( $font_family );
if ( ! empty( $handle ) ) {
$families[ $handle ] = true;
}
}

return ! empty( $families ) ? array_keys( $families ) : array();
}

/**
* Parse font families from theme.json.
*
* @since X.X.X
*
* @param array $settings Font settings to parse.
* @return array Returns an array that contains font data and corresponding handles.
*/
private static function parse_font_families( array $settings ) {
$handles = array();
$fonts = 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;
}

$font_family_slug = isset( $font_family['slug'] ) ? $font_family['slug'] : '';

foreach ( (array) $font_family['fontFace'] as $font_face ) {
// Skip if the font was registered through the Fonts API.
if ( isset( $font_face['origin'] ) && WP_Fonts::REGISTERED_ORIGIN === $font_face['origin'] ) {
continue;
}

// For each font "src", convert the "file:./" 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 font-face properties into kebab-case.
$font_face = static::to_kebab_case( $font_face );

$font_family_handle = static::get_font_family_handle( $font_family_slug, $font_face );

// Skip if no font-family handle was found.
if ( null === $font_family_handle ) {
continue;
}

$handles[] = $font_family_handle;
if ( ! array_key_exists( $font_family_handle, $fonts ) ) {
$fonts[ $font_family_handle ] = array();
}

$fonts[ $font_family_handle ][] = $font_face;
}
}
}

return array( $fonts, $handles );
}

/**
* Gets the font-family handle if defined in the "slug" or font-face variation.
*
* @since X.X.X
*
* @param string $font_family_slug Font-family "slug".
* @param array $font_face Font-face (variation) to search if the slug is empty.
* @return string|null Font-family handle on success, else null if not found.
*/
private static function get_font_family_handle( $font_family_slug, array $font_face ) {
// If the slug was defined in the theme's theme.json, return it as the handle.
if ( is_string( $font_family_slug ) && '' !== $font_family_slug ) {
return $font_family_slug;
}

// Else, get the font-family from the font-face variation.
$font_family_handle = WP_Fonts_Utils::get_font_family_from_variation( $font_face );

if ( empty( $font_family_handle ) ) {
$font_family_handle = WP_Fonts_Utils::convert_font_family_into_handle( $font_family_handle );
}

if ( ! is_string( $font_family_handle ) && empty( $font_family_handle ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Font family not defined in the variation or "slug".', 'gutenberg' ), '6.1.0' );
return null;
}

return $font_family_handle;
}

/**
* 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 X.X.X
*
* @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 X.X.X
*
* @param array $data The array to process.
*/
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;
}

/**
* Enqueues user-selected fonts via global styles.
*
Expand Down
11 changes: 10 additions & 1 deletion lib/experimental/fonts-api/class-wp-fonts.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
*/
class WP_Fonts extends WP_Dependencies {

/**
* Registered "origin", indicating the font is registered in the API.
*
* @since X.X.X
*
* @var string
*/
const REGISTERED_ORIGIN = 'gutenberg_wp_fonts_api';

/**
* An array of registered providers.
*
Expand Down Expand Up @@ -731,7 +740,7 @@ public function to_theme_json( $font_family_handle ) {
}

$variation_obj = $this->registered[ $variation_handle ];
$variation_properties = array( 'origin' => 'gutenberg_wp_fonts_api' );
$variation_properties = array( 'origin' => static::REGISTERED_ORIGIN );
foreach ( $variation_obj->extra['font-properties'] as $property_name => $property_value ) {
$property_in_camelcase = lcfirst( str_replace( '-', '', ucwords( $property_name, '-' ) ) );
$variation_properties[ $property_in_camelcase ] = $property_value;
Expand Down
9 changes: 9 additions & 0 deletions lib/experimental/fonts-api/fonts-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,12 @@ function( $mime_types ) {
return $mime_types;
}
);

/*
* `WP_Fonts_Resolver::register_fonts_from_theme_json()` calls `WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()`, which instantiates `WP_Theme_JSON_Gutenberg()`;
* Gutenberg server-side blocks are registered via the init hook with a priority value of `20`. E.g., `add_action( 'init', 'register_block_core_image', 20 )`;
* This priority value is added dynamically during the build. See: tools/webpack/blocks.js.
* Makes sure Gutenberg blocks are re-registered before any Theme_JSON operations take place
* so that there's access to updated merged data.
*/
add_action( 'init', 'WP_Fonts_Resolver::register_fonts_from_theme_json', 21 );
Loading