Skip to content

Commit

Permalink
Enqueue fonts used in theme.json
Browse files Browse the repository at this point in the history
  • Loading branch information
zaguiini committed Apr 2, 2022
1 parent f5f8339 commit 5c963e9
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
<?php

function gutenberg_get_family_indexes_from_theme_json( $font_families ) {
$font_families_by_index = array();

foreach ( $font_families as $index => $font_family ) {
$font_families_by_index[ WP_Webfonts::get_font_slug( $font_family ) ] = $index;
}

return $font_families_by_index;
}

function gutenberg_transform_font_face_to_camel_case( $font_face ) {
$camel_cased = array();

foreach ( $font_face as $key => $value ) {
$camel_cased[ lcfirst( str_replace( '-', '', ucwords( $key, '-' ) ) ) ] = $value;
}

return $camel_cased;
}

function gutenberg_transform_font_face_to_kebab_case( $font_face ) {
$new_face = array();

foreach ( $font_face as $key => $value ) {
$new_face[ _wp_to_kebab_case( $key ) ] = $value;
}

return $new_face;
}

function gutenberg_transform_font_faces_to_theme_json_format( $font_faces ) {
$transformed_font_faces = array();

foreach ( $font_faces as $font_face ) {
$transformed_font_faces[] = array_merge(
array(
'origin' => 'gutenberg_wp_webfonts_api',
'dynamicallyIncludedIntoThemeJSON' => true,
),
gutenberg_transform_font_face_to_camel_case( $font_face )
);
}

return $transformed_font_faces;
}

function gutenberg_transform_font_family_to_theme_json_format( $slug, $font_faces ) {
$family_name = $font_faces[0]['font-family'];

$providers_for_family = array();

foreach ( $font_faces as $font_face ) {
$providers_for_family[] = $font_face['provider'];
}

$providers_for_family = array_unique( $providers_for_family );

if ( 1 === count( $providers_for_family ) ) {
foreach ( $font_faces as $index => $font_face ) {
unset( $font_faces[ $index ]['provider'] );
}
}

$font_family_in_theme_json_format = array(
'origin' => 'gutenberg_wp_webfonts_api',
'dynamicallyIncludedIntoThemeJSON' => true,
'fontFamily' => str_contains( $family_name, ' ' ) ? "'{$family_name}'" : $family_name,
'name' => $family_name,
'slug' => $slug,
'fontFaces' => gutenberg_transform_font_faces_to_theme_json_format( $font_faces ),
);

if ( 1 === count( $providers_for_family ) ) {
$font_family_in_theme_json_format['provider'] = $providers_for_family[0];
}

return $font_family_in_theme_json_format;
}

function gutenberg_is_webfont_equal( $a, $b ) {
$equality_attrs = array(
'font-family',
'font-style',
'font-weight',
);

foreach ( $equality_attrs as $attr ) {
if ( $a[ $attr ] !== $b[ $attr ] ) {
return false;
}
}

return true;
}

function gutenberg_find_webfont( $webfonts, $webfont_to_find ) {
foreach ( $webfonts as $index => $webfont ) {
if ( gutenberg_is_webfont_equal( $webfont, $webfont_to_find ) ) {
return $index;
}
}

return false;
}

/**
* Add missing fonts data to the global styles.
*
* @param array $data The global styles.
* @return array The global styles with missing fonts data.
*/
function gutenberg_add_programmatically_registered_webfonts_to_theme_json( $data ) {
$programmatically_registered_font_families = wp_webfonts()->get_registered_webfonts();

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

$font_family_indexes_in_theme_json = gutenberg_get_family_indexes_from_theme_json( $data['settings']['typography']['fontFamilies'] );

foreach ( $programmatically_registered_font_families as $slug => $programmatically_registered_font_faces ) {
// This programmatically registered font family does not exist in theme.json, so let's add it, specifying its origin.
if ( ! isset( $font_family_indexes_in_theme_json[ $slug ] ) ) {
$data['settings']['typography']['fontFamilies'][] = gutenberg_transform_font_family_to_theme_json_format(
$slug,
$programmatically_registered_font_faces
);

continue;
}

// We know that the programmatically registered font family exists in theme.json at this point.

$font_family_index_in_theme_json = $font_family_indexes_in_theme_json[ $slug ];
$font_family_in_theme_json = $data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ];

/**
* The theme.json entry is specifying a provider at the top level, so that means it'll try to register the whole family later.
* Let's make theme.json take precedence over the API as the source of truth,
* and unregister the programmatically registered font family.
*/
if ( isset( $font_family_in_theme_json['provider'] ) ) {
wp_webfonts()->unregister_font_family_by_slug( $slug );
continue;
}

/**
* The entry in `theme.json` is not registering any font faces, so let's add them so them shows up in the editor.
*/
if ( ! isset( $font_family_in_theme_json['fontFaces'] ) ) {
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'] = gutenberg_transform_font_faces_to_theme_json_format( $programmatically_registered_font_faces );
continue;
}

/**
* There are font faces being registered, so let's add only the ones that are missing
*/

$font_faces_to_add_to_theme_json = $programmatically_registered_font_faces;

/**
* Let's un-register from the API the font faces that theme.json is going to register.
* Remember: theme.json is the source of truth here.
*/
foreach ( $font_family_in_theme_json['fontFaces'] as $index => $font_face_in_theme_json ) {
$font_face_to_register_index = gutenberg_find_webfont( $font_faces_to_add_to_theme_json, gutenberg_transform_font_face_to_kebab_case( $font_face_in_theme_json ) );

if ( isset( $font_face_in_theme_json['provider'] ) ) {
// It'll register the font face in theme.json, and we don't want to re-register it,
// so lets remove it from the API registry.
if ( false !== $font_face_to_register_index ) {
wp_webfonts()->unregister_font_face_by_index( $slug, $font_face_to_register_index );
unset( $font_faces_to_add_to_theme_json[ $font_face_to_register_index ] );
}

continue;
}

// If listed in theme.json, but found in programmatically registered font faces, let's signal it.
if ( false !== $font_face_to_register_index ) {
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'][ $index ] = $font_faces_to_add_to_theme_json[ $font_face_to_register_index ];
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'][ $index ]['origin'] = 'gutenberg_wp_webfonts_api';
// And remove from the list of font faces to add to the family in theme.json.
unset( $font_faces_to_add_to_theme_json[ $font_face_to_register_index ] );
continue;
}

trigger_error(
sprintf(
'The %s:%s:%s font face specified in theme.json is not registered programmatically, nor through theme.json.',
$font_face_in_theme_json['fontFamily'],
$font_face_in_theme_json['fontStyle'],
$font_face_in_theme_json['fontWeight'],
)
);
}

/**
* Finally, let's add the remaining programmatically registered font faces to the respective font family entry.
*/

foreach ( $font_faces_to_add_to_theme_json as $font_face_to_add_to_theme_json ) {
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'][] = array_merge(
array(
'origin' => 'gutenberg_wp_webfonts_api',
'dynamicallyIncludedIntoThemeJSON' => true,
),
gutenberg_transform_font_face_to_camel_case( $font_face_to_add_to_theme_json )
);
}
}

return $data;
}
4 changes: 2 additions & 2 deletions lib/experimental/class-wp-theme-json-resolver-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ public static function get_theme_data( $deprecated = array() ) {
if ( null === static::$theme ) {
$theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
$theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
$theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $theme_json_data );
$theme_json_data = gutenberg_add_programmatically_registered_webfonts_to_theme_json( $theme_json_data );
static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data );

if ( wp_get_theme()->parent() ) {
// Get parent theme.json.
$parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
$parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
$parent_theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $parent_theme_json_data );
$parent_theme_json_data = gutenberg_add_programmatically_registered_webfonts_to_theme_json( $parent_theme_json_data );
$parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data );

// Merge the child theme.json into the parent theme.json.
Expand Down
75 changes: 70 additions & 5 deletions lib/experimental/class-wp-webfonts.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ public function register_webfont( array $webfont ) {
/**
* Enqueue a font-family that has been already registered.
*
* @param string $font_family_name The font family name to be enqueued.
* @param string $font_family_name The font family name to be enqueued.
* @param array|null $font_face The font face to selectively enqueue.
* @return bool True if successfully enqueued, else false.
*/
public function enqueue_webfont( $font_family_name ) {
public function enqueue_webfont( $font_family_name, $font_face = null ) {
$slug = $this->get_font_slug( $font_family_name );

if ( isset( $this->enqueued_webfonts[ $slug ] ) ) {
if ( isset( $this->enqueued_webfonts[ $slug ] ) && ! isset( $this->registered_webfonts[ $slug ] ) ) {
trigger_error(
sprintf(
/* translators: %s unique slug to identify the font family of the webfont */
Expand All @@ -170,9 +171,73 @@ public function enqueue_webfont( $font_family_name ) {
return false;
}

$this->enqueued_webfonts[ $slug ] = $this->registered_webfonts[ $slug ];
if ( ! $font_face ) {
$this->enqueued_webfonts[ $slug ] = $this->registered_webfonts[ $slug ];
unset( $this->registered_webfonts[ $slug ] );

return true;
}

$registered_font_faces = $this->registered_webfonts[ $slug ];
$font_face = gutenberg_transform_font_face_to_kebab_case( $font_face );
$font_face_index = gutenberg_find_webfont( $registered_font_faces, $font_face );

if ( false === $font_face_index ) {
/* translators: %s unique slug to identify the font family of the webfont */
_doing_it_wrong( __METHOD__, sprintf( __( 'The specified font face for the "%s" font family is not registered.', 'gutenberg' ), $slug ), '6.0.0' );
return false;
}

if ( ! isset( $this->enqueued_webfonts[ $slug ] ) ) {
$this->enqueued_webfonts[ $slug ] = array();
}

$font_face_to_enqueue = $this->unregister_font_face_by_index( $slug, $font_face_index );

$this->enqueued_webfonts[ $slug ][] = $font_face_to_enqueue;
}

/**
* Checks if a font family is registered.
*
* @param string $font_family_name The font family name to check in the registry.
* @return bool True if found, else false.
*/
public function is_font_family_registered( $font_family_name ) {
return isset( $this->registered_webfonts[ $this->get_font_slug( $font_family_name ) ] );
}

/**
* Unregister a font family by its slug.
*
* @param string $slug The font family slug to unregister.
* @return array[] The font family object with font faces inside.
*/
public function unregister_font_family_by_slug( $slug ) {
$font_family = $this->registered_webfonts[ $slug ];
unset( $this->registered_webfonts[ $slug ] );
return true;

return $font_family;
}

/**
* Unregister a font face by its index.
*
* @param string $slug The font family to search for.
* @param integer $index The index of the font face that'll be unregistered.
* @return array The font face that was just unregistered.
*/
public function unregister_font_face_by_index( $slug, $index ) {
$font_face = $this->registered_webfonts[ $slug ][ $index ];

unset( $this->registered_webfonts[ $slug ][ $index ] );
$this->registered_webfonts[ $slug ] = array_values( $this->registered_webfonts[ $slug ] );

if ( empty( $this->registered_webfonts[ $slug ] ) ) {
unset( $this->registered_webfonts[ $slug ] );
}

return $font_face;
}

/**
Expand Down
52 changes: 52 additions & 0 deletions lib/experimental/enqueue-webfonts-from-theme-json.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

function gutenberg_enqueue_webfonts_from_theme_json() {
$theme_settings = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_settings();

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

// Look for fontFamilies.
foreach ( $theme_settings['typography']['fontFamilies'] as $font_families ) {
foreach ( $font_families as $font_family ) {
// Skip dynamically included font families. We only want to enqueue explicitly added fonts.
if ( isset( $font_family['dynamicallyIncludedIntoThemeJSON'] ) && true === $font_family['dynamicallyIncludedIntoThemeJSON'] ) {
continue;
}

// If no font faces defined.
if ( ! isset( $font_family['fontFaces'] ) ) {
// And the font family is registered.
if ( ! wp_webfonts()->is_font_family_registered( $font_family['fontFamily'] ) ) {
continue;
}

// And it was explicitly declared by the developer in theme.json.
if ( isset( $font_family['dynamicallyIncludedIntoThemeJSON'] ) ) {
continue;
}

// Enqueue the entire family.
wp_webfonts()->enqueue_webfont( $font_family );
continue;
}

// Loop through all the font faces, enqueueing each one of them.
foreach ( $font_family['fontFaces'] as $font_face ) {
wp_webfonts()->enqueue_webfont( $font_family, $font_face );
}
}
}
}

add_filter( 'wp_loaded', 'gutenberg_enqueue_webfonts_from_theme_json' );

// No need to run this -- opening the admin interface enqueues all the webfonts.
add_action(
'admin_init',
function() {
remove_filter( 'wp_loaded', 'gutenberg_enqueue_webfonts_from_theme_json' );
}
);
Loading

0 comments on commit 5c963e9

Please sign in to comment.