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] Proposal: classic themes define fonts in a theme.json file #51714

Closed
hellofromtonya opened this issue Jun 20, 2023 · 11 comments
Closed
Assignees
Labels
[Status] In discussion Used to indicate that an issue is in the process of being discussed [Type] Discussion For issues that are high-level and not yet ready to implement.

Comments

@hellofromtonya
Copy link
Contributor

hellofromtonya commented Jun 20, 2023

Part of #41479

Reference: See the "Update 19 Jun 2023" comment.

Within the fonts management system and Font Library, the Fonts API register, deregister, and enqueue functionality are no longer "publicly" needed. Plugins will directly interact with the Font Library (when that capability is available).

What about classic themes? How could they get their fonts into the Fonts API for server-side dynamic generation and printing of @font-face styles?

Previously before the Font Library, they would programmatically register and enqueue their fonts using wp_register_fonts() and wp_enqueue_fonts(). This made sense when the Fonts API was designed to allow plugins to introduce their fonts.

Now with the new fonts management / Font Library, these functions are no longer needed for plugins. For all theme.json powered sites, these public-facing functions / methods will be disabled or removed.

If those global functions did not exist, how would a classic theme, such as Twenty Twenty-One (TT1), get their fonts into the Fonts API? See the proposal.

Proposal for Classic Themes

This proposal proposes adopting the theme.json convention for classic themes to define their fonts for the Fonts API to generate and print the @font-face styles.

Classic themes, such as Twenty Twenty-One (TT1), will define their fonts by adding a theme.json file to their theme and defining only the fontFamilies section.

Here's an example:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"typography": {
			"fontFamilies": [
				{
					"fontFace": [
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Regular.woff2"
							]
						},
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Regular-Italic.woff2"
							]
						},
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "700",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Bold.woff2"
							]
						},
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "700",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Bold-Italic.woff2"
							]
						}
					],
					"fontFamily": "\"DM Sans\", sans-serif",
					"name": "DM Sans",
					"slug": "dm-sans"
				},
				{
					"fontFace": [
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "300",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Light.woff2"
							]
						},
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2"
							]
						},
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Italic.woff2"
							]
						},
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "700",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2"
							]
						}
					],
					"fontFamily": "'IBM Plex Mono', monospace",
					"name": "IBM Plex Mono",
					"slug": "ibm-plex-mono"
				},
				{
					"fontFace": [
						{
							"fontFamily": "Inter",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "200 900",
							"src": [
								"file:./assets/fonts/inter/Inter-VariableFont_slnt,wght.ttf"
							]
						}
					],
					"fontFamily": "\"Inter\", sans-serif",
					"name": "Inter",
					"slug": "inter"
				},
				{
					"fontFamily": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif",
					"name": "System Font",
					"slug": "system-font"
				},
				{
					"fontFace": [
						{
							"fontFamily": "Source Serif Pro",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "200 900",
							"src": [
								"file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2"
							]
						},
						{
							"fontFamily": "Source Serif Pro",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "200 900",
							"src": [
								"file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2"
							]
						}
					],
					"fontFamily": "\"Source Serif Pro\", serif",
					"name": "Source Serif Pro",
					"slug": "source-serif-pro"
				}
			]
		}
	}
}

The Benefits of This Approach

Without this approach, classic themes would need access to wp_register_fonts() and wp_enqueue_fonts(), meaning these public facing functions need logic to disable them for theme.json themes and logic to enable them for classic themes.

With this approach, all public facing functionality except for "printing" can either be removed or hidden as interworkings.

The benefits are:

  • No logic is needed to disable or enable the public facing register, deregister, enqueue, etc functionality.
  • WP_Dependencies is no longer needed as the basis of the architecture as it's overkill for its needs, given that a bulk of the functionality would need to be disabled.
  • Significantly less BC risks.
  • Less code.
  • Less complexity.

Does it work?

YES!

I tested a copy of TT1 where I added the font assets and a theme.json file to it. Here are the testing instructions for how I did it.

Yes, it works as expected:

  • The @font-face styles were generated and printed ✅
  • The theme was recognized to use Customizer instead of Site Editor ✅

Next steps

Consensus to move forward with adopting the theme.json approach for classic themes.

@hellofromtonya hellofromtonya added [Type] Discussion For issues that are high-level and not yet ready to implement. [Feature] Fonts API [Status] In discussion Used to indicate that an issue is in the process of being discussed labels Jun 20, 2023
@hellofromtonya hellofromtonya self-assigned this Jun 20, 2023
@hellofromtonya
Copy link
Contributor Author

A few pings to contributors involved with the Fonts API: @aristath @jonoalderson @ironprogrammer @anton-vlasenko @azaozz @annezazu.

@hellofromtonya
Copy link
Contributor Author

Test Report

Env:

  • WordPress: trunk
  • Gutenberg: trunk
  • Plugins: none other than Gutenberg
  • Localhost: wp-env
  • Theme: a copy of TT1 with some revisions (see set up instructions below)
  • Browser: Firefox and Chrome

Theme's Set up Instructions

  1. Copy Twenty Twenty-One (TT1) theme.

  2. Open style.css and change the "Theme Name" to "TT1 theme.json".

  3. Copy and paste the TT3 assets/fonts/ folder.

  4. Create a theme.json file in the root of the new theme's directory.

  5. Copy and paste the theme.json code. Save the file.

theme.json code to paste into the new theme
{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"typography": {
			"fontFamilies": [
				{
					"fontFace": [
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Regular.woff2"
							]
						},
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Regular-Italic.woff2"
							]
						},
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "700",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Bold.woff2"
							]
						},
						{
							"fontFamily": "DM Sans",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "700",
							"src": [
								"file:./assets/fonts/dm-sans/DMSans-Bold-Italic.woff2"
							]
						}
					],
					"fontFamily": "\"DM Sans\", sans-serif",
					"name": "DM Sans",
					"slug": "dm-sans"
				},
				{
					"fontFace": [
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "300",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Light.woff2"
							]
						},
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2"
							]
						},
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "400",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Italic.woff2"
							]
						},
						{
							"fontDisplay": "block",
							"fontFamily": "IBM Plex Mono",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "700",
							"src": [
								"file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2"
							]
						}
					],
					"fontFamily": "'IBM Plex Mono', monospace",
					"name": "IBM Plex Mono",
					"slug": "ibm-plex-mono"
				},
				{
					"fontFace": [
						{
							"fontFamily": "Inter",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "200 900",
							"src": [
								"file:./assets/fonts/inter/Inter-VariableFont_slnt,wght.ttf"
							]
						}
					],
					"fontFamily": "\"Inter\", sans-serif",
					"name": "Inter",
					"slug": "inter"
				},
				{
					"fontFamily": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif",
					"name": "System Font",
					"slug": "system-font"
				},
				{
					"fontFace": [
						{
							"fontFamily": "Source Serif Pro",
							"fontStretch": "normal",
							"fontStyle": "normal",
							"fontWeight": "200 900",
							"src": [
								"file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2"
							]
						},
						{
							"fontFamily": "Source Serif Pro",
							"fontStretch": "normal",
							"fontStyle": "italic",
							"fontWeight": "200 900",
							"src": [
								"file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2"
							]
						}
					],
					"fontFamily": "\"Source Serif Pro\", serif",
					"name": "Source Serif Pro",
					"slug": "source-serif-pro"
				}
			]
		}
	}
}
  1. Activate this theme.

Testing Instructions

  1. Confirm Customizer is the editor for the theme, not Site Editor. Go to Appearance > Themes. Click on "Customize" button on the theme. Customizer should open.
  2. Go to your test site's front-end.
  3. Using your favorite browser, inspect the HTML using its dev tools:
    • Find <style id="wp-fonts-local"> element in the <head>.
    • Inspect the CSS. It should include a @font-face style for each font in the theme.json.

Results

  • Yes, Customizer is the editor, not Site Editor ✅

tt1themejson-customizer

  • Yes, the @font-face styles were generated correctly ✅
<style id="wp-fonts-local">
@font-face{font-family:"DM Sans";font-style:normal;font-weight:400;font-display:fallback;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/dm-sans/DMSans-Regular.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"DM Sans";font-style:italic;font-weight:400;font-display:fallback;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/dm-sans/DMSans-Regular-Italic.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"DM Sans";font-style:normal;font-weight:700;font-display:fallback;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/dm-sans/DMSans-Bold.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"DM Sans";font-style:italic;font-weight:700;font-display:fallback;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/dm-sans/DMSans-Bold-Italic.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:300;font-display:block;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/ibm-plex-mono/IBMPlexMono-Light.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:400;font-display:block;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"IBM Plex Mono";font-style:italic;font-weight:400;font-display:block;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/ibm-plex-mono/IBMPlexMono-Italic.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:700;font-display:block;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:Inter;font-style:normal;font-weight:200 900;font-display:fallback;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/inter/Inter-VariableFont_slnt,wght.ttf') format('truetype');font-stretch:normal;}@font-face{font-family:"Source Serif Pro";font-style:normal;font-weight:200 900;font-display:fallback;src:url('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/source-serif-pro/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('http://localhost:8889/wp-content/themes/tt1-themejson/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2') format('woff2');font-stretch:normal;}
</style>

tt1themejson-fe

@hellofromtonya
Copy link
Contributor Author

Yesterday, I learned:

Since WordPress 6.0.0, a classic theme has had the ability to implement this proposal and it works. Yes, they've had the ability to define their fonts in a theme.json and the _wp_theme_json_webfonts_handler() stopgap code in WordPress Core properly generates and prints the @font-face styles for those fonts. Oh that's cool!

@hellofromtonya
Copy link
Contributor Author

Cross-sharing feedback from @felixarntz (originally shared on the Ongoing Roadmap) #41479 (comment):

as long as a classic theme can introduce support via theme.json only for fonts without having to also opt in to other block theme features, the new direction sounds great to me.

@ironprogrammer
Copy link
Contributor

ironprogrammer commented Jun 21, 2023

Test Report

This report tests the following scenarios to help confirm that classic theme support for custom fonts via theme.json works with Core's existing web font handling (even without GB enabled):

  • Custom font support via theme.json in a classic theme.
  • Custom font override via theme.json in a classic child theme.
  • Custom font inheritance to child via parent theme's theme.json.
  • Customizer support for custom fonts.

Setup Instructions

  1. Install the classic Seedlet theme, and the child theme Blank Canvas.
  2. Download the Oswald web font and extract the main variable font .ttf file.
  3. Convert the .ttf to .woff2, e.g. with Google's woff2 CLI utility.
  4. In the Seedlet assets/ directory, create a fonts/ folder and copy the new Oswald-VariableFont_wght.woff2 into it.
  5. In the Seedlet root directory, create a theme.json file, and add the custom font definition from this gist.
  6. In the Blank Canvas assets/ directory, create a fonts/ folder and copy the original Oswald-VariableFont_wght.ttf into it.
  7. In the Blank Canvas root directory, create a theme.json file, and add the custom font definition from this alternate gist.

Steps to Test

  1. Make sure the Gutenberg plugin is deactivated.
  2. Activate the Seedlet parent theme.
  3. Block Editor
    1. Open a post and insert or select a block (such as a heading).
    2. In the block settings, activate the "Font family" picker by clicking the three vertical dots next to "Typography". Confirm that Oswald appears in the picker.
    3. Set the font to Oswald. Confirm that the heading reflects the updated font.
    4. Inspect the <head> element. Confirm there is a <style> tag with the Oswald @font-face declaration (there is no class or ID attribute on this tag). Note that the local font has the .woff2 extension.
    5. Inspect the block (where the font was applied above). Confirm that the element includes the class has-oswald-font-family.
  4. Frontend
    1. Publish the post and view it on the frontend.
    2. Inspect the <head> element. Confirm there is a <style id="wp-webfonts-inline-css"> tag with the Oswald @font-face declaration.
    3. Inspect the block (where the font was applied in the editor). Confirm that the element includes the class has-oswald-font-family.
  5. Customizer
    1. In Appearance > Customize, select the "Additional CSS" menu item.
    2. Style H2 elements with the custom font by adding h2 { font-family: Oswald !important; }. Confirm that H2s reflect the custom font override.
  6. Activate the Blank Canvas child theme.
  7. Repeat Steps 3-5 above. Confirm that the child theme's font override is in effect by verifying that printed @font-faces refer to the local font's .ttf file, and that the font's behavior is consistent.
  8. Delete (or rename) the Blank Canvas theme.json file, then repeat Steps 3-5. Confirm that the child theme inherits the parent's font (.woff2) and that behavior is consistent.
  9. [Optional] Enable the Gutenberg plugin and repeat Steps 3-8. Confirm that behavior is consistent with the above. The only differences should be that the block editor is iframed, and that the <style> tag for @font-face definitions have the ID wp-fonts-local.

Environment

  • Hardware: MacBook Pro Apple M1 Pro
  • OS: macOS 13.4
  • Browser: Safari 16.5, Google Chrome 114.0.5735.133
  • Browser: Mozilla Firefox 114.0.1
  • Server: nginx/1.25.0
  • PHP: 7.4.33
  • WordPress: 6.3-alpha-55505-src
  • Themes: seedlet, blank-canvas
  • Active Plugins: gutenberg v16.0.0, both active/inactive

Results

  • ✅ Classic themes can define custom fonts via theme.json.
  • ✅ Classic child themes inherit custom fonts from their parent theme.
  • ✅ Classic child themes can override custom fonts via their own theme.json.
  • ✅ CSS in the Customizer can utilize the custom fonts.

@anton-vlasenko
Copy link
Contributor

anton-vlasenko commented Jun 21, 2023

I've noticed that adding a theme.json file to a non-block theme causes the theme to be marked as supporting block templates.
I'm wondering if marking a non-block theme as supporting block templates could create any potential issues.
I'm not able to spot any related issues at the moment, so this is just thinking out loud.

@hellofromtonya
Copy link
Contributor Author

I've noticed that adding a theme.json file to a non-block theme causes the theme to be marked as supporting block templates.
I'm wondering if marking a non-block theme as supporting block templates could create any potential issues.
I'm not able to spot any related issues at the moment, so this is just thinking out loud.

Thanks @anton-vlasenko. I didn't see any issues either.

To avoid any potential future conflicts, an approach might be to recommend classic themes remove the 'block-templates' theme support, i.e.

remove_theme_support( 'block-templates' );

Just need to figure out the timing for it.

@felixarntz
Copy link
Member

felixarntz commented Jun 21, 2023

@anton-vlasenko @hellofromtonya

I've noticed that adding a theme.json file to a non-block theme causes the theme to be marked as supporting block templates. I'm wondering if marking a non-block theme as supporting block templates could create any potential issues. I'm not able to spot any related issues at the moment, so this is just thinking out loud.

This is a concern I share. While tremendous work has been happening to bring performance enhancements to block theme performance in core (and while block themes overall are faster), on the server-side the block template handling comes at a sizable performance cost - which is acceptable given the additional features and benefits it comes with. But if having a theme.json is interpreted by core as a classic theme potentially having block templates, then those performance implications are "forced" upon classic themes that don't make use of block templates, in which case it is a performance cost without any benefits.

If having a theme.json is the only way to make use of the new font capabilities, I think it is crucial that we decouple the pure existence of a theme.json from other features that a classic theme may or may not use.

@hellofromtonya
Copy link
Contributor Author

New day, new learnings 😉

Classic themes and plugins will be able to programmatically pass their fonts for @font-face styles generation and printing. This means classic themes have 2 options:

  1. Add a theme.json file as per this proposal.
  2. Invoke and pass their fonts to the new "print" method.

Yesterday, I experimented with a redesign of the Fonts API, transforming it into only a Font Face styles generator and printer #51770. It exposes a print method (i.e. WP_Font_Face::print( $fonts ) that accepts an array of fonts. No register or enqueue - just pass it the fonts to process.

This new design then supports all types of sites for both plugins and themes.

@hellofromtonya
Copy link
Contributor Author

I think it is crucial that we decouple the pure existence of a theme.json from other features that a classic theme may or may not use.

I think this is interesting! Not sure what is needed though.

@hellofromtonya
Copy link
Contributor Author

With the redesign of Fonts API to expose a print( $font ) method that accepts an array of fonts, I'm thinking this particular proposal is no longer needed. Classic themes (and plugins) will have a programmatic way to get their fonts @font-face styles generated and printed.

I think this issue's proposal as written - i.e. to benefit only the Fonts API's redesign - is no longer needed, as the redesign can solve the problem #51769. I'll close this proposal.

That said, giving classic themes the option to define their fonts in theme.json file is interesting. It'll need more exploration to discover and then decouple block specific functionality and features. This kind of exploration is out-of-scope for this particular issue, thus needs a new issue to capture that discovery. If you agree, please feel free to open a new issue for it.

Thank you everyone for your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Status] In discussion Used to indicate that an issue is in the process of being discussed [Type] Discussion For issues that are high-level and not yet ready to implement.
Projects
None yet
Development

No branches or pull requests

4 participants