Skip to content

Commit 5c963e9

Browse files
committed
Enqueue fonts used in theme.json
1 parent f5f8339 commit 5c963e9

6 files changed

+388
-91
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<?php
2+
3+
function gutenberg_get_family_indexes_from_theme_json( $font_families ) {
4+
$font_families_by_index = array();
5+
6+
foreach ( $font_families as $index => $font_family ) {
7+
$font_families_by_index[ WP_Webfonts::get_font_slug( $font_family ) ] = $index;
8+
}
9+
10+
return $font_families_by_index;
11+
}
12+
13+
function gutenberg_transform_font_face_to_camel_case( $font_face ) {
14+
$camel_cased = array();
15+
16+
foreach ( $font_face as $key => $value ) {
17+
$camel_cased[ lcfirst( str_replace( '-', '', ucwords( $key, '-' ) ) ) ] = $value;
18+
}
19+
20+
return $camel_cased;
21+
}
22+
23+
function gutenberg_transform_font_face_to_kebab_case( $font_face ) {
24+
$new_face = array();
25+
26+
foreach ( $font_face as $key => $value ) {
27+
$new_face[ _wp_to_kebab_case( $key ) ] = $value;
28+
}
29+
30+
return $new_face;
31+
}
32+
33+
function gutenberg_transform_font_faces_to_theme_json_format( $font_faces ) {
34+
$transformed_font_faces = array();
35+
36+
foreach ( $font_faces as $font_face ) {
37+
$transformed_font_faces[] = array_merge(
38+
array(
39+
'origin' => 'gutenberg_wp_webfonts_api',
40+
'dynamicallyIncludedIntoThemeJSON' => true,
41+
),
42+
gutenberg_transform_font_face_to_camel_case( $font_face )
43+
);
44+
}
45+
46+
return $transformed_font_faces;
47+
}
48+
49+
function gutenberg_transform_font_family_to_theme_json_format( $slug, $font_faces ) {
50+
$family_name = $font_faces[0]['font-family'];
51+
52+
$providers_for_family = array();
53+
54+
foreach ( $font_faces as $font_face ) {
55+
$providers_for_family[] = $font_face['provider'];
56+
}
57+
58+
$providers_for_family = array_unique( $providers_for_family );
59+
60+
if ( 1 === count( $providers_for_family ) ) {
61+
foreach ( $font_faces as $index => $font_face ) {
62+
unset( $font_faces[ $index ]['provider'] );
63+
}
64+
}
65+
66+
$font_family_in_theme_json_format = array(
67+
'origin' => 'gutenberg_wp_webfonts_api',
68+
'dynamicallyIncludedIntoThemeJSON' => true,
69+
'fontFamily' => str_contains( $family_name, ' ' ) ? "'{$family_name}'" : $family_name,
70+
'name' => $family_name,
71+
'slug' => $slug,
72+
'fontFaces' => gutenberg_transform_font_faces_to_theme_json_format( $font_faces ),
73+
);
74+
75+
if ( 1 === count( $providers_for_family ) ) {
76+
$font_family_in_theme_json_format['provider'] = $providers_for_family[0];
77+
}
78+
79+
return $font_family_in_theme_json_format;
80+
}
81+
82+
function gutenberg_is_webfont_equal( $a, $b ) {
83+
$equality_attrs = array(
84+
'font-family',
85+
'font-style',
86+
'font-weight',
87+
);
88+
89+
foreach ( $equality_attrs as $attr ) {
90+
if ( $a[ $attr ] !== $b[ $attr ] ) {
91+
return false;
92+
}
93+
}
94+
95+
return true;
96+
}
97+
98+
function gutenberg_find_webfont( $webfonts, $webfont_to_find ) {
99+
foreach ( $webfonts as $index => $webfont ) {
100+
if ( gutenberg_is_webfont_equal( $webfont, $webfont_to_find ) ) {
101+
return $index;
102+
}
103+
}
104+
105+
return false;
106+
}
107+
108+
/**
109+
* Add missing fonts data to the global styles.
110+
*
111+
* @param array $data The global styles.
112+
* @return array The global styles with missing fonts data.
113+
*/
114+
function gutenberg_add_programmatically_registered_webfonts_to_theme_json( $data ) {
115+
$programmatically_registered_font_families = wp_webfonts()->get_registered_webfonts();
116+
117+
// Make sure the path to settings.typography.fontFamilies.theme exists
118+
// before adding missing fonts.
119+
if ( empty( $data['settings'] ) ) {
120+
$data['settings'] = array();
121+
}
122+
if ( empty( $data['settings']['typography'] ) ) {
123+
$data['settings']['typography'] = array();
124+
}
125+
if ( empty( $data['settings']['typography']['fontFamilies'] ) ) {
126+
$data['settings']['typography']['fontFamilies'] = array();
127+
}
128+
129+
$font_family_indexes_in_theme_json = gutenberg_get_family_indexes_from_theme_json( $data['settings']['typography']['fontFamilies'] );
130+
131+
foreach ( $programmatically_registered_font_families as $slug => $programmatically_registered_font_faces ) {
132+
// This programmatically registered font family does not exist in theme.json, so let's add it, specifying its origin.
133+
if ( ! isset( $font_family_indexes_in_theme_json[ $slug ] ) ) {
134+
$data['settings']['typography']['fontFamilies'][] = gutenberg_transform_font_family_to_theme_json_format(
135+
$slug,
136+
$programmatically_registered_font_faces
137+
);
138+
139+
continue;
140+
}
141+
142+
// We know that the programmatically registered font family exists in theme.json at this point.
143+
144+
$font_family_index_in_theme_json = $font_family_indexes_in_theme_json[ $slug ];
145+
$font_family_in_theme_json = $data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ];
146+
147+
/**
148+
* The theme.json entry is specifying a provider at the top level, so that means it'll try to register the whole family later.
149+
* Let's make theme.json take precedence over the API as the source of truth,
150+
* and unregister the programmatically registered font family.
151+
*/
152+
if ( isset( $font_family_in_theme_json['provider'] ) ) {
153+
wp_webfonts()->unregister_font_family_by_slug( $slug );
154+
continue;
155+
}
156+
157+
/**
158+
* The entry in `theme.json` is not registering any font faces, so let's add them so them shows up in the editor.
159+
*/
160+
if ( ! isset( $font_family_in_theme_json['fontFaces'] ) ) {
161+
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'] = gutenberg_transform_font_faces_to_theme_json_format( $programmatically_registered_font_faces );
162+
continue;
163+
}
164+
165+
/**
166+
* There are font faces being registered, so let's add only the ones that are missing
167+
*/
168+
169+
$font_faces_to_add_to_theme_json = $programmatically_registered_font_faces;
170+
171+
/**
172+
* Let's un-register from the API the font faces that theme.json is going to register.
173+
* Remember: theme.json is the source of truth here.
174+
*/
175+
foreach ( $font_family_in_theme_json['fontFaces'] as $index => $font_face_in_theme_json ) {
176+
$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 ) );
177+
178+
if ( isset( $font_face_in_theme_json['provider'] ) ) {
179+
// It'll register the font face in theme.json, and we don't want to re-register it,
180+
// so lets remove it from the API registry.
181+
if ( false !== $font_face_to_register_index ) {
182+
wp_webfonts()->unregister_font_face_by_index( $slug, $font_face_to_register_index );
183+
unset( $font_faces_to_add_to_theme_json[ $font_face_to_register_index ] );
184+
}
185+
186+
continue;
187+
}
188+
189+
// If listed in theme.json, but found in programmatically registered font faces, let's signal it.
190+
if ( false !== $font_face_to_register_index ) {
191+
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'][ $index ] = $font_faces_to_add_to_theme_json[ $font_face_to_register_index ];
192+
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'][ $index ]['origin'] = 'gutenberg_wp_webfonts_api';
193+
// And remove from the list of font faces to add to the family in theme.json.
194+
unset( $font_faces_to_add_to_theme_json[ $font_face_to_register_index ] );
195+
continue;
196+
}
197+
198+
trigger_error(
199+
sprintf(
200+
'The %s:%s:%s font face specified in theme.json is not registered programmatically, nor through theme.json.',
201+
$font_face_in_theme_json['fontFamily'],
202+
$font_face_in_theme_json['fontStyle'],
203+
$font_face_in_theme_json['fontWeight'],
204+
)
205+
);
206+
}
207+
208+
/**
209+
* Finally, let's add the remaining programmatically registered font faces to the respective font family entry.
210+
*/
211+
212+
foreach ( $font_faces_to_add_to_theme_json as $font_face_to_add_to_theme_json ) {
213+
$data['settings']['typography']['fontFamilies'][ $font_family_index_in_theme_json ]['fontFaces'][] = array_merge(
214+
array(
215+
'origin' => 'gutenberg_wp_webfonts_api',
216+
'dynamicallyIncludedIntoThemeJSON' => true,
217+
),
218+
gutenberg_transform_font_face_to_camel_case( $font_face_to_add_to_theme_json )
219+
);
220+
}
221+
}
222+
223+
return $data;
224+
}

lib/experimental/class-wp-theme-json-resolver-gutenberg.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ public static function get_theme_data( $deprecated = array() ) {
3434
if ( null === static::$theme ) {
3535
$theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
3636
$theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
37-
$theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $theme_json_data );
37+
$theme_json_data = gutenberg_add_programmatically_registered_webfonts_to_theme_json( $theme_json_data );
3838
static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data );
3939

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

4747
// Merge the child theme.json into the parent theme.json.

lib/experimental/class-wp-webfonts.php

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,14 @@ public function register_webfont( array $webfont ) {
145145
/**
146146
* Enqueue a font-family that has been already registered.
147147
*
148-
* @param string $font_family_name The font family name to be enqueued.
148+
* @param string $font_family_name The font family name to be enqueued.
149+
* @param array|null $font_face The font face to selectively enqueue.
149150
* @return bool True if successfully enqueued, else false.
150151
*/
151-
public function enqueue_webfont( $font_family_name ) {
152+
public function enqueue_webfont( $font_family_name, $font_face = null ) {
152153
$slug = $this->get_font_slug( $font_family_name );
153154

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

173-
$this->enqueued_webfonts[ $slug ] = $this->registered_webfonts[ $slug ];
174+
if ( ! $font_face ) {
175+
$this->enqueued_webfonts[ $slug ] = $this->registered_webfonts[ $slug ];
176+
unset( $this->registered_webfonts[ $slug ] );
177+
178+
return true;
179+
}
180+
181+
$registered_font_faces = $this->registered_webfonts[ $slug ];
182+
$font_face = gutenberg_transform_font_face_to_kebab_case( $font_face );
183+
$font_face_index = gutenberg_find_webfont( $registered_font_faces, $font_face );
184+
185+
if ( false === $font_face_index ) {
186+
/* translators: %s unique slug to identify the font family of the webfont */
187+
_doing_it_wrong( __METHOD__, sprintf( __( 'The specified font face for the "%s" font family is not registered.', 'gutenberg' ), $slug ), '6.0.0' );
188+
return false;
189+
}
190+
191+
if ( ! isset( $this->enqueued_webfonts[ $slug ] ) ) {
192+
$this->enqueued_webfonts[ $slug ] = array();
193+
}
194+
195+
$font_face_to_enqueue = $this->unregister_font_face_by_index( $slug, $font_face_index );
196+
197+
$this->enqueued_webfonts[ $slug ][] = $font_face_to_enqueue;
198+
}
199+
200+
/**
201+
* Checks if a font family is registered.
202+
*
203+
* @param string $font_family_name The font family name to check in the registry.
204+
* @return bool True if found, else false.
205+
*/
206+
public function is_font_family_registered( $font_family_name ) {
207+
return isset( $this->registered_webfonts[ $this->get_font_slug( $font_family_name ) ] );
208+
}
209+
210+
/**
211+
* Unregister a font family by its slug.
212+
*
213+
* @param string $slug The font family slug to unregister.
214+
* @return array[] The font family object with font faces inside.
215+
*/
216+
public function unregister_font_family_by_slug( $slug ) {
217+
$font_family = $this->registered_webfonts[ $slug ];
174218
unset( $this->registered_webfonts[ $slug ] );
175-
return true;
219+
220+
return $font_family;
221+
}
222+
223+
/**
224+
* Unregister a font face by its index.
225+
*
226+
* @param string $slug The font family to search for.
227+
* @param integer $index The index of the font face that'll be unregistered.
228+
* @return array The font face that was just unregistered.
229+
*/
230+
public function unregister_font_face_by_index( $slug, $index ) {
231+
$font_face = $this->registered_webfonts[ $slug ][ $index ];
232+
233+
unset( $this->registered_webfonts[ $slug ][ $index ] );
234+
$this->registered_webfonts[ $slug ] = array_values( $this->registered_webfonts[ $slug ] );
235+
236+
if ( empty( $this->registered_webfonts[ $slug ] ) ) {
237+
unset( $this->registered_webfonts[ $slug ] );
238+
}
239+
240+
return $font_face;
176241
}
177242

178243
/**
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
function gutenberg_enqueue_webfonts_from_theme_json() {
4+
$theme_settings = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_settings();
5+
6+
// Bail out early if there are no settings for webfonts.
7+
if ( empty( $theme_settings['typography'] ) || empty( $theme_settings['typography']['fontFamilies'] ) ) {
8+
return;
9+
}
10+
11+
// Look for fontFamilies.
12+
foreach ( $theme_settings['typography']['fontFamilies'] as $font_families ) {
13+
foreach ( $font_families as $font_family ) {
14+
// Skip dynamically included font families. We only want to enqueue explicitly added fonts.
15+
if ( isset( $font_family['dynamicallyIncludedIntoThemeJSON'] ) && true === $font_family['dynamicallyIncludedIntoThemeJSON'] ) {
16+
continue;
17+
}
18+
19+
// If no font faces defined.
20+
if ( ! isset( $font_family['fontFaces'] ) ) {
21+
// And the font family is registered.
22+
if ( ! wp_webfonts()->is_font_family_registered( $font_family['fontFamily'] ) ) {
23+
continue;
24+
}
25+
26+
// And it was explicitly declared by the developer in theme.json.
27+
if ( isset( $font_family['dynamicallyIncludedIntoThemeJSON'] ) ) {
28+
continue;
29+
}
30+
31+
// Enqueue the entire family.
32+
wp_webfonts()->enqueue_webfont( $font_family );
33+
continue;
34+
}
35+
36+
// Loop through all the font faces, enqueueing each one of them.
37+
foreach ( $font_family['fontFaces'] as $font_face ) {
38+
wp_webfonts()->enqueue_webfont( $font_family, $font_face );
39+
}
40+
}
41+
}
42+
}
43+
44+
add_filter( 'wp_loaded', 'gutenberg_enqueue_webfonts_from_theme_json' );
45+
46+
// No need to run this -- opening the admin interface enqueues all the webfonts.
47+
add_action(
48+
'admin_init',
49+
function() {
50+
remove_filter( 'wp_loaded', 'gutenberg_enqueue_webfonts_from_theme_json' );
51+
}
52+
);

0 commit comments

Comments
 (0)