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] Automatically enqueue all user-selected fonts picked in Global Styles. #40363

Closed
zaguiini opened this issue Apr 14, 2022 · 14 comments · Fixed by #50297 or #50529
Closed

[Fonts API] Automatically enqueue all user-selected fonts picked in Global Styles. #40363

zaguiini opened this issue Apr 14, 2022 · 14 comments · Fixed by #50297 or #50529
Assignees
Labels
[Priority] High Used to indicate top priority items that need quick attention
Projects

Comments

@zaguiini
Copy link
Contributor

zaguiini commented Apr 14, 2022

Enhancement Scope:

To add automatic detection and enqueueing of all user-selected global fonts before printing the fonts (likely within wp_print_fonts()).

What are "user-selected global fonts"?

Fonts that a user selected and saved from the Site Editor > Styles > Typography UI.
Screen Shot 2023-05-03 at 11 09 44 AM

They are "global" because a selection in this UI is intended for global availability throughout the website and content.

How are these fonts made available for user selection?

Fonts defined within a theme's theme.json file are already automatically registered and enqueued within the Fonts API.

For plugins and theme's without a theme.json file, the API does not provide the mechanism to detect or enqueue their fonts when a user selects them.

What problem does this address?

🔹 Plugins and theme's without a theme.json file have been required to provide the detection and enqueuing of their user-selected fonts. This enhancement moves that mechanism and functionality into the Fonts API so that detection and enqueuing are within the API.

Why are the benefits?

  • Easier for extenders: extenders will no longer need to provide or maintain this functionality in their products.
  • Makes the out-of-box enqueuing process more consistent, stable, and predictable.

What is your proposed solution?

Before printing the fonts (likely within the wp_fonts_print(), i.e. as late as possible:

  1. Fetch all of the user-selected global fonts (those fonts that the user picked and saved from the Site Editor > Styles > Typography). Font: Need the font-family handles.
  2. Enqueue those font families.
@Addison-Stavlo
Copy link
Contributor

Addison-Stavlo commented Apr 18, 2022

enqueue programmatically webfonts picked in global styles

Should this be part of the scope of this PR: Webfonts: enqueue fonts listed in theme.json ? IIRC the goal was to enqueue fonts that were saved in theme json (global styles cpt is the saved theme json customizations), so fonts in the base theme.json or saved theme.json/global-styles cpt should be enqueued?

@hellofromtonya hellofromtonya added this to To do in Fonts API via automation Jun 1, 2022
@hellofromtonya hellofromtonya removed the [Priority] High Used to indicate top priority items that need quick attention label Jun 23, 2022
@hellofromtonya hellofromtonya added the [Priority] High Used to indicate top priority items that need quick attention label Jan 18, 2023
@hellofromtonya hellofromtonya self-assigned this Jan 18, 2023
@hellofromtonya hellofromtonya moved this from Backlog to In progress in Fonts API Jan 18, 2023
@hellofromtonya hellofromtonya added the [Status] In Progress Tracking issues with work in progress label Jan 18, 2023
@hellofromtonya hellofromtonya changed the title Webfonts: enqueue programmatically webfonts picked in global styles Apr 6, 2023
@hellofromtonya hellofromtonya moved this from In progress to In Discussion in Fonts API Apr 27, 2023
@hellofromtonya hellofromtonya moved this from In Discussion to In progress in Fonts API Apr 27, 2023
@hellofromtonya hellofromtonya moved this from In progress to In Discussion in Fonts API Apr 27, 2023
@hellofromtonya
Copy link
Contributor

Currently working on a POC. Here are some notes on where user selected fonts exist in the global styles.

How to get the global styles?

Use wp_get_global_styles(), which returns an array of 'elements' and general global styles such as 'typography'. Let's call this $global_styles.

Where are the font-families defined?

That depends on the user selections in Site Editor > Styles > Typography.
Screen Shot 2023-05-03 at 11 09 44 AM

If user selected, each of the following "Elements" are in the wp_get_global_styles() returned array:

  • Text: $global_styles['typography']['fontFamily']
  • Links: $global_styles['elements']['link']['typography']['fontFamily']
  • Headings: $global_styles['elements']['heading']['typography']['fontFamily']
  • Captions: $global_styles['elements']['caption']['typography']['fontFamily']
  • Buttons: $global_styles['elements']['button']['typography']['fontFamily']

Notice a pattern?

  • There's a relationship between the "Elements" available for user selection in the Site Editor's Style Typography UI. Currently, the elements are: Text, Links, Headings, Captions, and Buttons.
  • All Element user selections are in the 'elements' array expect for the Text Element.
  • All of the user selections are found in ['typography']['fontFamily'].

What's the format of the 'fontFamily'?

var:preset|font-family|<font-family-handle>

The font-family's handle is the identifier for enqueuing the Fonts API

The font-family handle is the identifier of the font-family and its variations within the Fonts API, i.e. those registered in the API. To enqueue, the handle for each user selection is needed.

Implementations

One approach:
Using wp_get_global_styles(), the implementation would need to pluck (or iterate to) all of the ['typography']['fontFamily'] for the selectable global styles elements and then parse the handle for each.

Need to explore:

  • how to get the list of selectable "Elements" (i.e. used for the Site Editor's Styles > Typography UI). This list of selectable elements can be used to directly point in the 'elements' array, eliminating the need to iterate through all of the elements (i.e. for performance).
  • where to get the fontFamily style structure from the Global Styles Engine. Doing this could eliminate impacts and code maintenance burdens should that structure change.

Any existing implementations?

Jetpack's Google Fonts Provider has an implementation.

@hellofromtonya hellofromtonya moved this from In Discussion to In progress in Fonts API May 3, 2023
@hellofromtonya hellofromtonya added this to the Gutenberg 15.8 milestone May 3, 2023
@hellofromtonya hellofromtonya moved this from In progress to In review in Fonts API May 4, 2023
Fonts API automation moved this from In review to Done May 9, 2023
@hellofromtonya
Copy link
Contributor

Reopening this enhancement as more time is needed to understand and discuss the user selected global fonts identification.

Why?

@oandregal alerted me that wp_get_global_styles() includes the stack of styles (theme, core, user) and not just the user selections. He noticed it after merging PR #50366 when the tests failed.

#50297 was reverted (via #50512) to get tests working again in trunk and give more time for understanding, discussion, and next steps for this enhancement.

Fonts API automation moved this from Done to In progress May 10, 2023
@hellofromtonya
Copy link
Contributor

I've updated this enhancement's description for its scope and intent.

@hellofromtonya
Copy link
Contributor

How should the Fonts API Resolver identify the user-selected global fonts?

@oandregal proposed using WP_Theme_JSON_Resolver_Gutenberg::get_user_data()->get_raw_data(); (i.e. in PR #50499) instead of wp_get_global_styles(). Why?

  • wp_get_global_styles() does indeed fetch the global styles, but it's a combination of core, theme, and user data merged together as it uses WP_Theme_JSON_Resolver_Gutenberg::get_merged_data().
  • WP_Theme_JSON_Resolver_Gutenberg::get_user_data() returns only the user data (i.e. stored in the CPT and/or via the filter).

Aha, I understand now. Rather than using ::get_merged_data(), this enhancement needs to use ::get_user_data() to fetch only the user's data rather than the combined/merged data. That makes sense.

Is it only for the global user data? Yes, as it fetches the CPT.

I'll recreate the enhancement PR for the Resolver using a combination of PR #50297 and #50499. I'll also look at more test datasets to ensure there's plenty of scenario coverage.

@oandregal
Copy link
Member

I've started to look into the fonts API to understand what we are trying to do here. From what I've learned so far, I see three issues:

  1. The Font Families API works differently than any other preset. This is a blocker from my point of view.
  2. How extenders can provide more font families.
  3. How to enqueue only the font families in use.

1. The Font Families API works differently than any other preset.

I've described at #50576 a major issue I've seen: the font families API only works with the theme origin. It should work with any origin, following the behavior of any other preset (font sizes, colors, etc.).

This makes the API do things that are contrary to the theme.json model and their different origins. This is the major issue I see and the first we need to address, from my point of view.

2. How extenders can provide more font families.

Fonts defined within a theme's theme.json file are already automatically registered and enqueued within the Fonts API.

For plugins and theme's without a theme.json file, the API does not provide the mechanism to detect or enqueue their fonts when a user selects them.

When the font families API works with any origin this is already solved. There are a few approaches and we can discuss them when this first issue is addressed. I'm sharing some that I see now to kick-start the conversation:

function register_font_families_from_plugin( $theme_json ){
	$new_data = array(
		'version'  => 2,
		'settings' => array(
			'typography' => array(
				'fontFamilies'    => array( /* New font family */
					array(
						'slug'           => 'new-font-family',
						'name'         => __( 'New font family', 'translation-domain' ),
                                                'fontFamily' => '...',
                                                'fontFace' => array( ... ) 
					),
				),
			),
		),
	);

	return $theme_json->update_with( $new_data );
}
add_filter( 'wp_theme_json_data_theme', 'register_font_families_from_plugin' );
  • Another option is to create a PHP function to declare their font families. WordPress will take this info and will automatically register them as part of the theme.json. This is what we do for some "theme supports". See docs and code.

3. How to enqueue only the font families in use.

A third topic is: how do we make sure we only enqueue the fonts in use instead of them all. This is important when plugins extend the font family list to add hundreds of them from different providers (google, type foundries, etc.).

This is also easily solved when the 1st issue is addressed.

The approach would be what Tonya developed for #50529 We inspect the styles of the merged theme.json (data from default, theme, and custom) and only enqueue the ones in use. Note that there are edge cases, such as themes/plugin using the fonts in a CSS stylesheet. In this case, they'll have to develop their own mechanism for enqueuing the font.

@hellofromtonya
Copy link
Contributor

How extenders can provide more font families.

Plugins and themes without a theme.json register their font-families and variations using the Fonts API wp_register_fonts(). Why?

How to enqueue only the font families in use.

I think this is best answered by sharing the scope and vision @mtias laid out (shared in the Ongoing Roadmap's description). The Fonts API will:

  • Provide automatic registration and enqueueing of fontFaces defined in a theme's theme.json file. The theme is the starting point for typography definition.
  • Provide the means for further customization by providing global functionality for plugins and themes without a theme.json file to register and enqueue their font-family and variations.

This issue is focused on the custom enqueueing of user-selected fonts (that were picked in the Site Editor > Styles > Typography) that are not defined in the theme's theme.json file.

I've described at #50576 a major issue I've seen: the font families API only works with the theme origin. It should work with any origin, following the behavior of any other preset (font sizes, colors, etc.).

This makes the API do things that are contrary to the theme.json model and their different origins. This is the major issue I see and the first we need to address, from my point of view.

I'm not seeing how though this is a blocker for the scope of this specific enhancement (i.e. the automatic detection and enqueueing of user-selected global fonts).

Rather, please correct if I'm wrong @oandregal, it seems more like a broader discussion of the vision and scope of the Fonts API. Maybe that discussion should shift to #50576, rather than blocking this enhancement. Why? If #50576 results in a broader change, it will impact more than enqueuing. Right? It'll impact the registration process and widen the detection of merged origins for automatic enqueuing.

Also noting, the output of the Fonts API is the @font-face CSS generation that are printed for the iframed editors and front-end. This generation requires font-family src files or URL and property definitions. A theme and possibly plugins provide this information to the API via registration and carry the local src files for those fonts or a provider with the logic for a remote font foundry such as Google Fonts.

@oandregal can you provide more specifics on how or why this is blocking this specific enhancement with its scope?

@oandregal
Copy link
Member

I had a conversation with Tonya where she shared more of the context and how the font API works. This is what I've learned:

  • We enqueue automatically only the fonts themes declare via their theme.json. This is, data coming from settings.typography.fontFamily.theme.
  • Plugins or themes without theme.json need to enqueue the fonts themselves. To make plugins' life easier, we aim to enqueue any font the user's selected via the Typography panels available at the Global Styles Sidebar. This is, data coming from styles.typography (any node, but only for the custom origin). This is what #50529 aims to implement.
@oandregal
Copy link
Member

oandregal commented May 15, 2023

Based on what I learned:

  1. The Font Families API works differently than any other preset. I still find the behavior at 50576 problematic. It should not block this work. We can continue discussion there. Either we make any origin work the same, or we make sure default and custom have no data.

  2. How extenders can provide more font families. This already works with a function. I tested this by using the test case https://github.com/ironprogrammer/webfonts-jetpack-test

  3. How to enqueue only the font families in use. I've updated my view on this: the gist is what are the font families in use?.

When a plugin registers more fonts, these are available in any typography panel control: both in the "global styles sidebar" AND in the block inspector of any block that supports font family. If core wants to help plugins, it's not enough to enqueue the fonts picked by the user in the "global styles sidebar", core also needs to enqueue the fonts picked by the user for a given block via its block inspector. It sounds like 50529 addresses the former, but not the latter.

@hellofromtonya
Copy link
Contributor

hellofromtonya commented May 15, 2023

Thank you @oandregal for summarizing our conversation.

I think there's a misunderstanding though:

When a plugin registers more fonts, these are available in any typography panel control: both in the "global styles sidebar" AND in the block inspector of any block that supports font family. If core wants to help plugins, it's not enough to enqueue the fonts picked by the user in the "global styles sidebar", core also needs to enqueue the fonts picked by the user for a given block via its block inspector. It sounds like #50529 addresses the former, but not the latter.

This PR is enqueuing the user-selected fonts at the global level, making them available for block level typography selection.

All registered fonts are shown in the Site Editor > Styles > Typography for user selection as "global fonts". These are the fonts that get enqueued by this PR.

core also needs to enqueue the fonts picked by the user for a given block via its block inspector.
Actually, at the block level, only enqueued fonts should be displayed for user selection in the typography pickers. Currently, there's a bug that is showing registered fonts at the block level (here is the bug report).

Thinking through the fonts layers:

  • Global fonts:
    • Theme: All theme defined fonts in the theme's theme.json file are automatically made global as the API enqueues them.
    • User selected global fonts: fonts a user selects in the Site Editor > Styles > Typography UI get enqueued, if they aren't already.
  • Block level:
    • Uses the global fonts (i.e. enqueued fonts): users can select specific typography at the block level. But the typography pickers are (or will be once the bug is fixed) populated with only global (enqueued) fonts. Thus there's no additional enqueuing required.

Does this make sense @oandregal?

@oandregal
Copy link
Member

Thanks for the additional context.

I expected that we'd do differently: all registered fonts would be available in every design tool (global styles and block inspector) and only the fonts in use would be enqueued in the front-end (any available in styles.typography.fontFamily for any theme.json not only the user's PLUS any font selected at block-level). Note that this is how plugins, such as the one you shared, work today:

  1. enqueue the fonts selected via theme.json (all origins, not only the user) https://github.com/Automattic/jetpack-google-fonts-provider/blob/trunk/src/introspectors/class-global-styles.php#L21
  2. enqueue the fonts selected by the user at the block level https://github.com/Automattic/jetpack-google-fonts-provider/blob/trunk/src/introspectors/class-blocks.php#L24

I see now how the goal is different from this. If we don't allow fonts for 2 (or only the subset that are also selected at 1), we don't need that code. 50529 would be enough when 40362 is also implemented.

However, I find that we may run into some edge cases with the model we aim to implement. For example:

  • Users select the font MY_FONT at the global styles sidebar.
  • Then they go to the block inspector of some block. Because MY_FONT is now available there, they select it for that block.
  • They go to the global styles sidebar and unselect MY_FONT, effectively making it unavailable.

What does happen at this point? If we only enqueue the fonts that are selected in the global styles sidebar, MY_FONT won't be enqueued in the front-end. Though it's in use.

@oandregal
Copy link
Member

Also, please, don't feel #50529 should be blocked by this conversation. I see it has two approvals from people that know fonts more than I do. It should be fine to ship. We can iterate if this conversation uncovers issues.

@oandregal
Copy link
Member

I've talked with Matías and learned a few more things about what we aim to do with the font library:

  • font library and font registration — (what you can see and access in GS for any site).
  • font selection — what the user picks to use in their site, determines enqueueing.
  • font block use — in block types, elements, and block instances, the reference is ideally just an ordered font-1, font-2 (assuming weight and style is coming from the other attributes) that is resolved to the layer 2 of font selection.

Font library work can be seen at #45271 and related issues.

The core idea is that fonts in use by the blocks (serialized as post content) would be abstracted (font-1, font-2, etc.) and not the font family itself (dm-mono, ibm-plex, etc.) Hence, if a font family is unregistered, there will be another that will be picked up. In this model, there's still the edge case of having 5 fonts registered that the user picks for specific blocks, then the user switches to only 3 fonts registered. By that point, font-4 and font-5 won't be available. This is an issue that can be looked at later, when we cross that bridge.

I'll comment on #50576 about the data flow, so it doesn't hijack this issue no longer.

@hellofromtonya
Copy link
Contributor

hellofromtonya commented May 16, 2023

Also, please, don't feel #50529 should be blocked by this conversation. I see it has two approvals from people that know fonts more than I do. It should be fine to ship. We can iterate if this conversation uncovers issues.

This is an issue that can be looked at later, when we cross that bridge.

Double-checking with you @oandregal. After your conversation with Matías, are you okay with this PR's scope and implementation? Do you still feel it is no longer blocked?

Fonts API automation moved this from In review to Done May 22, 2023
@hellofromtonya hellofromtonya removed this from the Gutenberg 15.8 milestone May 22, 2023
@priethor priethor removed the [Status] In Progress Tracking issues with work in progress label May 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Priority] High Used to indicate top priority items that need quick attention
5 participants