Make WordPress Core

Opened 6 years ago

Closed 16 months ago

Last modified 16 months ago

#46370 closed feature request (reported-upstream)

Fonts API: a proposal for creating an API to register and enqueue web fonts

Reported by: jonoaldersonwp's profile jonoaldersonwp Owned by: hellofromtonya's profile hellofromTonya
Milestone: Priority: normal
Severity: normal Version:
Component: General Keywords: needs-testing-info gutenberg-merge
Focuses: performance, privacy Cc:

Description (last modified by jonoaldersonwp)

The problem

Loading custom fonts in a sub-optimal manner can cause significant performance and privacy issues. Anecdotally, inefficient font loading is frequently one of the biggest performance bottlenecks in many WordPress themes/sites.

This is, in part, because there hasn’t been a ‘best practice’ approach; there’s no ‘standard’ way of adding a font to a theme and ensuring that it’s loaded in a performance- and privacy-friendly manner.

In almost all cases, theme/plugin developers either enqueue a third-party stylesheet (e.g., Google Fonts), or, bundle and enqueue the CSS for a local font. Both approaches cause problems. In particular:

  • Loading third-party resources can raise privacy concerns.
  • Loading fonts from external sources means that WordPress is often unable to optimize those fonts (no DNS prefetching, no intelligent browser prioritisation, no de-duplication, etc).
  • Bundling/enqueuing local fonts puts a strong reliance on the theme/plugin author understanding the complex nuances of efficient font-loading.

Even our own sites and setups (wordpress.org, the WordPress admin area, Gutenberg, and some of our core themes) fall afoul of these issues. They're slow, and they have privacy problems.

If we’re serious about WordPress becoming a fast, privacy-friendly platform, we can’t rely on theme developers to add and manage fonts without providing a framework to support them.

Why now?

It’s only recently that CSS and performance technologies/methodologies have matured sufficiently to define a ‘right way’ to load fonts.

In particular:

  • Best practices for defining and loading fonts via CSS are now well-established, stable, and see broad usage elsewhere.
  • The increasing adoption of HTTP/2 means that localising assets can (in many cases) be faster than loading them from remote sources.
  • There is increasing discomfort with WordPress 'passively endorsing' Google Fonts, which 'leaks' private information (IP addresses).

Now, standards like Google's Web Fundamentals documentation (which much of this spec is directly lifted and adapted from) describe a stable, compatible, and plug-and-play approach to loading fonts.

There are well-defied, standardised approaches to loading fonts, which we can implement into WordPress core. This will allow theme and plugin developers to bypass all of these pitfalls.

The vision

Theme developers shouldn’t have to manage their own font loading and optimisation. The registering and loading of fonts should be managed in the same way that we manage CSS and JavaScript - via abstraction, through an enqueue system.

Whilst fonts have more moving parts than script and style files, I believe that wp_enqueue_font() could become a robust, standardised way of adding custom fonts to themes without radical effort or change - with all of the advantages we’re used to from abstracting JS/CSS (conditional logic, dependency management, versioning/cache-busting, consolidation of duplicates, etc).

A move to an enqueue-based approach may also provide plugin developers with hooks to intercept the default behaviours, and to modify the output. E.g., to utilise the font loading API rather than outputting CSS. This provides a huge opportunity for caching and performance-optimising plugins to speed up WordPress sites/themes.

In its simplest form, wp_enqueue_font() provides a thin abstraction over existing WP mechanisms (wp_enqueue_style()), wp_add_inline_style(), etc), and simply enforces a performant, privacy-friendly implementation.

The ‘Web Fundamentals’ approach

The Web Fundamentals documentation provides an example of an optimal CSS structure for loading fonts. E.g.,

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  font-display: fallback;
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2'),
       url('/fonts/awesome-l.woff') format('woff'),
       url('/fonts/awesome-l.ttf') format('truetype'),
       url('/fonts/awesome-l.eot') format('embedded-opentype');
  unicode-range: U+000-5FF;
}

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 700;
  font-display: fallback;
  src: local('Awesome Font'),
       url('/fonts/awesome-l-700.woff2') format('woff2'),
       url('/fonts/awesome-l-700.woff') format('woff'),
       url('/fonts/awesome-l-700.ttf') format('truetype'),
       url('/fonts/awesome-l-700.eot') format('embedded-opentype');
  unicode-range: U+000-5FF;
}

In this example, a single font (‘Awesome Font’) is being loaded, in two different weights (400 and 700). For each of those weights, multiple formats are provided, prioritised in such a way that considers and is optimized for browser support. The approach for different styles (e.g., italic), and all other variations, follow this pattern.

In addition to using an optimal CSS approach, the documentation recommends using a <link rel="preload"> directive (or equivalents JavaScript-based approaches). This prevents the browser from having to wait until the render tree is complete before downloading font resources.

Our aim is to enable all themes/plugins to achieve this type of approach and to output this approach (the optimized CSS, paired with a preload directive) without requiring developers to explicitly craft and maintain this code.

A spec

The process of enqueuing a font should allow a developer to specify the name of the font, and to provide an optional level of detail/control over specific aspects (versions, weights, file locations). Where details/specifics aren’t provided, sensible fallbacks and defaults should be assumed.

I suggest the following structure function definition:

<?php
/**
 * Enqueue font.
 *
 * @param string            $family The name of the font family.
 * @param string|array|bool $src    The source(s) of the font files.
 * @param array $params {
 *      Params.
 *
 *      @type string        $style     The style of the font.
 *      @type int           $weight    The weight of the font.
 *      @type string        $display   Display/swap behaviour.
 *      @type string|array  $variation Variation settings.
 *      @type string        $range     A unicode range value.
 *      @type string|bool   $external  Externalise the CSS file/code.
 *      @type bool          $preload   Should the resource be preloaded.
 *      @type bool          $in_footer Output the CSS/file in the footer.
 *      @type string|bool   $media     Apply a media query to the output.
 * }
 */
function wp_enqueue_font( $family, $src = false, $params = array() ) {
        $params = wp_parse_args(
                $params,
                array(
                        'style'                   => 'normal',
                        'weight'                  => 400,
                        'display'                 => 'fallback',
                        'font-variation-settings' => normal,
                        'range'                   => false,
                        'external'                => false,
                        'preload'                 => true,
                        'in_footer'               => false,
                        'media'                   => false
                )
        );

        // ...

Note that the args after $src are in an associative array to avoid having to supply positional params with default values and to make it easier to easily identify the parameters. It's worth considering that something similar could be done for wp_register_script() and wp_register_style().

Starting from simplicity

A simple implementation of this, which omits much of the detail and utilises default behaviours, might look something like:

<?php
wp_enqueue_font(
  'Awesome Font',
  '/fonts/awesome-font-400.woff2'
);

In this example, we’ve specified the bare minimum information required to enqueue a font - a family and a location. But there’s enough here that we can generate the following CSS:

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome-font-400.woff2') format('woff2'),
}

We can simplify even further, though. If no $src value is defined, then a local src can still be referenced via the $family. Note that this will only work if the user's system has the font installed locally. E.g.:

wp_enqueue_font('Awesome Font');

Produces the following:

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font');
}

Registering and Enqueue'ing

As with scripts and styles, we should allow for registration and enqueuing as separate processes.

I.e., wp_register_font() should accept the same parameters as wp_enqueue_font(). Filters/hooks should be introduced to enable theme/plugin authors to manipulate the behaviour of fonts between these stages.

Behind the scenes, there's some additional complexity here. Scripts and styles use a $handle to identify them uniquely. Fonts don't have an equivalent concept (multiple font variations may share the same family namespace), so we need to synthesize one. This will allow us to pass gracefully wrap functions like wp_enqueue_style().

To achieve this, we should combine the (sanitized) $family, $style, $weight and $media strings to create a unique representation; e.g., create_font_handle($family.$style.$weight.$media).

If multiple fonts are registered with the same handle, the last-enqueued version should take priority (overwriting previous enqueues).

De-regestering and de-queue'ing

As we've highlighted, the $handle is sometimes insufficient to represent a unique font (for the purposes of identification, registration, and conflict management).

This means that wp_dequeue_font() and wp_deregister_font() should accept an optional array of values in addition to the handle. E.g.,

/**
 * Dequeue font.
 *
 * @param string            $family The family of the font.
 * @param array|false       $params {
 *      Params.
 *
 *      @type string        $style     The style of the font.
 *      @type string        $weight    The weight of the font.
 *      @type string        $media     The media query for the font.
 * }
*/

If only a string is passed, all fonts matching that $family name should be removed. If an array is passed, then only fonts matching the family and the passed params should be dequeued/deregistered.

Definitions

Some of the properties we're using here are lifted directly from wp_enqueue_style(), and should be treated identically and passed directly through to that function (e.g., $in_footer, $media). Some are fairly self-descriptive, and shouldn't need any special consideration (e.g., $range).

The unique and more complex remaining properties are explored below.

$src

Whilst our $src variable can accept a simple string, more advanced usage should specify multiple versions and their formats. That looks like this (maintaining the rest of our ‘simplest implementation’ approach):

<?php
wp_enqueue_font(
  'Awesome Font',
  array(
    'woff2'             => '/fonts/awesome-font-400.woff2',
    'woff'              => '/fonts/awesome-font-400.woff',
    'truetype'          => '/fonts/awesome-font-400.ttf',
    'embedded-opentype' => '/fonts/awesome-font-400.eot',
  )
);
  • NOTE: If a type is invalid, the declaration should be ignored.
  • NOTE: Data URLs (e.g., data:application/font-woff2;charset=utf-8;base64[...]) may be provided instead of file paths.

The source values may be either a local or absolute URL (including remote URLs). However, this spec assumes (and prefers) that plugins and themes should generally store their font files locally; e.g., /wp-content/themes/{{theme_name}}/fonts/ or /wp-content/plugins/{{plugin_name}}/fonts/.

$variation

There's a maturing standard around 'variable fonts', which adds a 'font-variation-settings' property. This accepts a string of key/value pairs, which define attributes like the font's weight, slant, or other variations.

The $variation property accepts either a string (normal), or, an array of key/value pairs (e.g., ["wght" => 637, "wdth" => 100]), and returns a string of these values (e.g., wght 637, wdth 100).

$external

When the $external flag is false (which is the default behaviour), the generated CSS should be output in the <head>, via wp_add_inline_style() (unless $in_footer is set to true, in which case, the code should be output in a generated <style> tag hooked into wp_footer).

When $external is set to true, we should use wp_register_style() and wp_enqueue_style() to reference a procedurally generated CSS file containing the relevant CSS at /wp-content/fonts/$handle.css.

Because we can't rely on having write permission to that folder (or know that it even exists), we should _not_ try to create a physical file, but rather, utilise a URL rewrite similar to the approach used in do_robots() to serve /robots.txt.

Note that this assumes the standardisation of /wp-content/fonts/ as a protected space. New WordPress installations should generate this (empty) folder.

When $external is set to a string, we should use the string value as the endpoint to reference (e.g., /wp-content/plugins/{{some-plugin}}/fonts/custom-endpoint.css).

$preload

When the $preload flag is true (which is the default behaviour), wp_resource_hints* should be filtered to add a <link rel="preload"> directive for the most modern format font file in the $src array (usually woff2).

If $external is set to true, the associated CSS file should also be preloaded.

  • NOTE: The preload tag is currently unsupported by wp_resource_hints, and it's expected that another (similar) function may be introduced to support this. Discussion here.

Other considerations

Invalid values & minimum requirements

Invalid or malformed values for parameters with constrained values (e.g., $style, $weight, $display) should fall back to their defaults.

Invalid or malformed values for parameters without constrained values (e.g., $range, $src) should be ignored.

If this validation results in there being no values for $family and $src (which represents the bare minimum requirements), no CSS should be generated/output.

Hooks & filters

Filters/hooks should be introduced to enable theme/plugin authors to manipulate the behaviour of fonts between each of the stages we've defined - registration, enqueuing, and output (in various formats/locations).

These will need to be defined.

Next steps

There are lots of moving parts on this one, but I’m hoping that most of it is fairly straightforward. I’d love some feedback on (any gaps in / issues with) the spec.

I’m anticipating that we'll need a bunch of discussion, iteration, exploration and definition before it makes sense to start authoring any code on this one, but that said, it’d be super to see some of this start to take shape.

Change History (149)

#1 @ocean90
6 years ago

  • Summary changed from A proposal for creating wp_enqueue_font() to A proposal for creating an API to register and enqueue web fonts

#2 @ocean90
6 years ago

#46020 was marked as a duplicate.

#3 in reply to: ↑ description @jonoaldersonwp
6 years ago

Clarification: $src values should accept a local OR absolute URL, which allows for loading (and filtering, etc) of remotely hosted fonts, CDNs, etc.

Addition: We're hinting strongly from a privacy and performance basis that Google Fonts isn't a privacy-friendly or performance-friendly (without consideration and management - the kinds of which are often lacking, and which precipitated this spec). However, there's nothing stopping people from ignoring this function, and, continuing to / additionally loading Google Fonts resources.

This ticket was mentioned in Slack in #core-privacy by garrett-eclipse. View the logs.


6 years ago

#5 @jonoaldersonwp
6 years ago

*Update*: Props to @westonruter, swapped the $src array ordering, to expect type => loc.

#6 @westonruter
6 years ago

  • Description modified (diff)

#7 @westonruter
6 years ago

  • Description modified (diff)

I've updated the proposed definition of wp_enqueue_font() to pass args as an array instead of many positional params.

Last edited 6 years ago by westonruter (previous) (diff)

This ticket was mentioned in Slack in #core-editor by jonoaldersonwp. View the logs.


6 years ago

This ticket was mentioned in Slack in #core-privacy by jonoaldersonwp. View the logs.


6 years ago

#10 @jonoaldersonwp
6 years ago

Note that, this assumes that as a user, I may still download fonts from foundries (e.g., Google Fonts) - where that's permissible -and use them as local versions through this approach.

This ticket was mentioned in Slack in #themereview by williampatton. View the logs.


6 years ago

#12 @westonruter
6 years ago

I've opened another incremental ticket with patch to implement Google Fonts new font-display capability: #47282.

This ticket was mentioned in Slack in #core-privacy by jonoaldersonwp. View the logs.


5 years ago

This ticket was mentioned in Slack in #core-privacy by jonoaldersonwp. View the logs.


5 years ago

This ticket was mentioned in Slack in #themereview by poena. View the logs.


5 years ago

This ticket was mentioned in Slack in #core by joostdevalk. View the logs.


5 years ago

This ticket was mentioned in Slack in #meta by jonoaldersonwp. View the logs.


5 years ago

This ticket was mentioned in Slack in #core by jonoaldersonwp. View the logs.


5 years ago

#21 @jonoaldersonwp
5 years ago

Just updated with some significant refinements and clarifications.

#22 @jonoaldersonwp
5 years ago

  • Description modified (diff)
  • Focuses privacy added

#23 @jonoaldersonwp
5 years ago

  • Description modified (diff)

#24 @jonoaldersonwp
5 years ago

  • Description modified (diff)

Updated the default font-display value to fallback. See #47282 for rationale.

Last edited 5 years ago by SergeyBiryukov (previous) (diff)

This ticket was mentioned in Slack in #core-privacy by carike. View the logs.


5 years ago

This ticket was mentioned in Slack in #forums by carike. View the logs.


5 years ago

This ticket was mentioned in Slack in #core-privacy by jonoaldersonwp. View the logs.


5 years ago

This ticket was mentioned in Slack in #core-privacy by carike. View the logs.


4 years ago

#30 follow-ups: @jb510
4 years ago

This looks great and thanks @garrett-eclipse for pointing to that post and the WPTT code for downloading Google Fonts to local https://github.com/WPTT/webfont-loader

My only question about this ticket is will it be usable by commercially licensed fonts as well? ie. could Adobe Fonts/TypeKit make a plugin that offered their catalog of fonts and still leveraged this API? I don't off hand know what their needs would be, maybe just that the output be filterable enough to tack on a API token/license key where needed.

#31 in reply to: ↑ 30 ; follow-up: @garrett-eclipse
4 years ago

Replying to jb510:

My only question about this ticket is will it be usable by commercially licensed fonts as well? ie. could Adobe Fonts/TypeKit make a plugin that offered their catalog of fonts and still leveraged this API? I don't off hand know what their needs would be, maybe just that the output be filterable enough to tack on a API token/license key where needed.

Sadly I don't know enough to answer that but @aristath may.

#32 in reply to: ↑ 31 @arena
4 years ago

Replying to garrett-eclipse:

Replying to jb510:

My only question about this ticket is will it be usable by commercially licensed fonts as well? ie. could Adobe Fonts/TypeKit make a plugin that offered their catalog of fonts and still leveraged this API? I don't off hand know what their needs would be, maybe just that the output be filterable enough to tack on a API token/license key where needed.

Sadly I don't know enough to answer that but @aristath may.

Nice !

Definitely, there should be a == Privacy == section in readme.txt for themes and plugins explaining if personal data could or are collected.

  • external web services
  • local storage and for what purpose
  • if is compliant with WordPress consent api
  • if is compliant with WordPress Export/Erase Personnal Data process
  • any other relevant information about personal data

#33 @aristath
4 years ago

My only question about this ticket is will it be usable by commercially licensed fonts as well? ie. could Adobe Fonts/TypeKit make a plugin that offered their catalog of fonts and still leveraged this API? I don't off hand know what their needs would be, maybe just that the output be filterable enough to tack on a API token/license key where needed.

In its first iteration the current code does not support that. However, adding an extra argument in one of the calls to allow tweaking the request headers (and therefore allow API authentication) is something we can definitely do.

This ticket was mentioned in PR #1573 on WordPress/wordpress-develop by aristath.


3 years ago
#34

  • Keywords has-patch has-unit-tests added

This PR adds functions for a webfonts API and is a first iteration covering the basics, something we can build upon in future patches to add more features and expand on what we do here.

  • wp_register_webfont
  • wp_deregister_webfont
  • wp_enqueue_webfont
  • wp_dequeue_webfont
  • wp_webfont_is
  • wp_webfont_add_data

The syntax of all these functions is identical to their style counterparts, so wp_register_webfont is the same as wp_register_style and so on. The only difference is the use of $params in lieu of $deps for practical reasons (see below)

Notes:

  • The styles registered for webfonts automatically get a webfont- prefix
  • Since webfonts don't have dependencies, the $deps argument was replaced wirth $params. These params can be used to register a webfont from local files, and auto-generates the CSS for @font-face.

Example 1: enqueuing a webfont from an API like google-fonts - or any other API that provides CSS
{{{php
add_action( 'wp_enqueue_scripts', function() {

wp_enqueue_webfont(

The handle
'dancing-script',
URL to the webfont CSS - can use any public API.
'https://fonts.googleapis.com/css2?family=Dancing+Script:wght@500;600&display=swap',

);

} );
}}}

Example 2: enqueueing a webfont from local files
{{{php
add_action( 'wp_enqueue_scripts', function() {

wp_enqueue_webfont( 'my-font', , array(

'font-family' => 'My Font',
'font-display' => 'swap',
'font-style' => 'normal',
'font-weight' => '400',
'src' => array(

get_template_directory_uri() . '/fonts/font.woff2',
get_template_directory_uri() . '/fonts/font.woff',

),

) );

} );

}}}
---

In case we're generating the CSS from $params, we can skip the empty string used for $src just to make it a bit cleaner, so instead of wp_enqueue_webfont( 'my-font', '', array(...) ); we can do wp_enqueue_webfont( 'my-font', array(...) );

## Registering a webfont from an API with CSS files

Most APIs provide CSS for their webfonts. Registering a webfont in this manner is easy as we can simply call wp_enqueue_webfont( $handle, $url );. No extra args are required, this is the simplest scenario.

## Registering a webfont from local files

To register a webfont from local files, we can use the $params arg. This is formatted as an array and accepts all valid CSS props of @font-face as its array keys. Any extra args are ignored. The list of valid descriptors was taken from MDN.
Using a font-family is mandatory, and skipping that results in no CSS getting generated.

### The src

If we only want to define a single file for the webfont, then we can add it as a string ('src' => $url).
If we have multiple files for the webfont (different formats to support older browsers), then we can use an array ('src' => [https://caniuse.com/svg-fonts $url1, $url2 ]). In this case, the URLs get internally reordered for browser support (woff2, woff, ttf, eot, otf). SVG for webfonts is not supported because they have been deprecated (see [caniuse.com/svg-fonts]), so if provided it gets removed (like any other invalid type).

Note: The src can also accept data-urls.

### Variable fonts

The font-variation-settings property accepts either a string (normal), or, an array of key/value pairs (e.g., ["wght" => 637, "wdth" => 100]), and returns a string of these values (e.g., wght 637, wdth 100).

### Preloading

Preloading webfonts is enabled by default. To disable it, we can add 'preload' => false to the $params. When enabled, the 1st item in src (which is the most modern format since we changed their order when parsing the params) gets a <link rel="preload" href="%1$s" as="font" type="%2$s" crossorigin> added to <head> (usually that will be the .woff2 file).

Ticket: https://core.trac.wordpress.org/ticket/46370


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

This ticket was mentioned in Slack in #core-editor by aristath. View the logs.


3 years ago

aristath commented on PR #1573:


3 years ago
#36

Note: As discussed on https://wordpress.slack.com/archives/C02QB2JS7/p1631135661385600 we should be able to register a local webfont from a theme.json file. When this patch gets merged we'll need to submit a PR in Gutenberg to allow registering a webfont when parsing the theme.json file.

#37 in reply to: ↑ 30 @skorasaurus
3 years ago

Replying to jb510:

This looks great and thanks @garrett-eclipse for pointing to that post and the WPTT code for downloading Google Fonts to local https://github.com/WPTT/webfont-loader

My only question about this ticket is will it be usable by commercially licensed fonts as well? ie. could Adobe Fonts/TypeKit make a plugin that offered their catalog of fonts and still leveraged this API? I don't off hand know what their needs would be, maybe just that the output be filterable enough to tack on a API token/license key where needed.

I currently use Adobe Fonts/Typekit at my workplace; they provide a custom URL CSS file that you're instructed to load into your site.
A sample CSS File is at https://gist.github.com/skorasaurus/203092b49e323d23fbd894ca65cf09e2 (some integers in the file have been modified for security purposes)

#38 @flixos90
3 years ago

I left a comment in https://make.wordpress.org/core/2021/09/28/implementing-a-webfonts-api-in-wordpress-core/#comment-41880, but wanted to cross-post here: The proposed API looks solid to me from a perspective of bundled fonts, but the approach has limitations when it comes to API-based fonts.

When loading API-based fonts, it is possible to load multiple fonts at a time, so using wp_enqueue_webfont like that feels off from an API perspective. It may even be preferred to e.g. make one request to https://fonts.googleapis.com/css2 with 6 fonts rather than three requests to https://fonts.googleapis.com/css2 with 2 fonts each. In addition, the two usages currently function quite differently, essentially using one function but with entirely different ways to call it.

If the API supported a way to register a fonts API "provider" and specify details like root URL and possibly even how it combines font parameters as part of that registration, we could then enqueue such web fonts in a more intuitive way that is closer to how the API works for bundled fonts. For example (given there is a provider "google"):

<?php
wp_enqueue_webfont( 'my-font', '', array(
    'font-family'  => 'My Font',
    'font-display' => 'swap',
    'font-style'   => 'normal',
    'font-weight'  => '400',
    'provider'     => 'google',
) );

This ticket was mentioned in Slack in #hosting-community by javier. View the logs.


3 years ago

#40 @aristath
3 years ago

@flixos90 I refactored the patch and the suggestion from above now works like this:

<?php
wp_enqueue_webfont( 'roboto-400', '', array(
    'font-family'  => 'Roboto',
    'font-display' => 'swap',
    'font-style'   => 'normal',
    'font-weight'  => '400',
    'provider'     => new WP_Fonts_Provider_Google(),
) );

#41 @flixos90
3 years ago

Thanks @aristath, that usage looks more intuitive to me. I'll leave further comments on the PR itself, that should be easier to discuss there.

hellofromtonya commented on PR #1573:


3 years ago
#42

I left some additional feedback on the fonts provider implementation. I'm still leaning towards it making more sense to register a fonts provider outside of the wp_register_webfont / wp_enqueue_webfont functions and then in those calls only referencing a registered provider. Breaking out the registration may especially make sense given my other point below, that provider classes should also be able to "orchestrate" multiple fonts together.

My first impressions are:

  • This is too complex for theme authors
  • Bridging theme.json into this API current implementation could challenging and would force undesired complexity to wrangle the flavor of schema into this registration and enqueuing process

I propose we start with the end in mind (i.e. theme.json webfonts collection schema) and then build the baseline (Phase 1) of this API as a stepping stone towards that future. In doing so, the concept of declaring the webfonts a theme is using becomes more straightforward with less code and steps for theme authors. It also can include registering custom providers and combining of like fonts for optimizing fetching and processing.

The discussion is captured here in Ari's fork. Feedback is welcomed and encouraged. If consensus, then the next version of this API (for Phase 1) can be built rather quickly for review.

felixarntz commented on PR #1573:


3 years ago
#43

@hellofromtonya

This is too complex for theme authors

Which part you mean is too complex? I'm envisioning the API to be the same as what's proposed in #4. Registering a webfont provider isn't something that a theme author would typically be expected to do. I think core should provide the common webfont providers (e.g. local, Google, Adobe) out of the box, so registration would only be necessary when using a less popular or entirely custom provider (let's say if a company had their own fonts CDN). Maybe that has been unclear in my comment before, but registering a webfont provider wouldn't be required as long as you use one of the common ones that WP core would provide.

My proposed approach would facilitate the same API that is currently descibed in #4, where when registering a webfont you simply need to specify a provider slug (like "local", "google", "adobe",...), or it would default to the "local" one probably.

Feedback is welcomed and encouraged. If consensus, then the next version of this API (for Phase 1) can be built rather quickly for review.

Could you clarify what is "Phase 1"? If we want to implement certain parts later, we should still ensure the architecture we decide on now will allow for that. Specifically about webfont provider registration, I agree we could also build the registration part later, which would mean that in the beginning you would really only be able to use the webfont providers that WP core implements out of the box.

hellofromtonya commented on PR #1573:


3 years ago
#44

Registering a webfont provider isn't something that a theme author would typically be expected to do. I think core should provide the common webfont providers (e.g. local, Google, Adobe) out of the box, so registration would only be necessary when using a less popular or entirely custom provider (let's say if a company had their own fonts CDN). Maybe that has been unclear in my comment before, but registering a webfont provider wouldn't be required as long as you use one of the common ones that WP core would provide.

I agree. Core should have built-in providers that it registers. Theme authors would then specify which provider via its associated ID such as local, google, etc.

For custom things, a custom provider can be registered as you suggested previously.

Which part you mean is too complex?

  • Individually registering each font within the theme using wp_register_webfont() or wp_enqueue_webfont()
  • Instantiating a provider as part of each of these registrations/enqueuing within the theme
  • A theme author having to figure out if they configure via src or params or both

With the new proposal being discussed, it aligns better to the future need while reducing code and complexity for theme authors.

My proposed approach would facilitate the same API that is currently descibed in 4, where when registering a webfont you simply need to specify a provider slug (like "local", "google", "adobe",...), or it would default to the "local" one probably.

I agree. That's the vision of the proposal. The theme defines all of the fonts it'll use and for each font, it associates a slug or ID for the provider. Core will expose built-in providers. Custom providers can be built and registered.

  • local => handles local fonts
  • google => handles fetching from the Google Fonts API
  • my-custom-font-provider => handles a custom provider (that is in the theme)

felixarntz commented on PR #1573:


3 years ago
#45

  • Individually registering each font within the theme using wp_register_webfont() or wp_enqueue_webfont()

The currently proposed functions wp_register_webfont() and wp_enqueue_webfont() make the lower-level API, that doesn't mean we have to require theme authors to call those functions. Theme authors could specify directives in theme.json that would then be translate to calls to those functions. The functions would need to be available still, e.g. for plugin authors, who wouldn't have theme.json as a mechanism.

  • Instantiating a provider as part of each of these registrations/enqueuing within the theme

That would never be necessary. When calling the function, no provider needs to be instantiated, only the identifier for a core default provider would be passed. Registration of a provider wouldn't be necessary either unless it's a custom one.

  • A theme author having to figure out if they configure via src or params or both

As far as I've seen based on the latest API iteration (also see https://core.trac.wordpress.org/ticket/46370#comment:40), I don't think the differentiation between src and params is a thing anymore? Or am I missing something @aristath? Is there a place where you would still provide only src and not params?

hellofromtonya commented on PR #1573:


3 years ago
#46

Could you clarify what is "Phase 1"?

That's a great question. I had thoughts here where we're initially discussing the new proposal.

Overview of Phase 1:

  • Input: the webfonts collection schema in an array data type (for registration)
  • Processing:
    • Schema validation
    • Collection of like font providers to optimize fetching from external APIs
    • Registration of providers (including custom providers)
    • Generation of CSS
  • Output: CSS

This includes:

  • the functions to register the webfonts collection and custom providers
  • built-in Core providers

What's not included:

  • Handling, parsing, or validation of the theme.json
  • An Adobe Fonts Provider

If we want to implement certain parts later, we should still ensure the architecture we decide on now will allow for that. Specifically about webfont provider registration, I agree we could also build the registration part later, which would mean that in the beginning you would really only be able to use the webfont providers that WP core implements out of the box.

I agree. What gets built now should be a stepping stone towards the future.

hellofromtonya commented on PR #1573:


3 years ago
#47

The currently proposed functions wp_register_webfont() and wp_enqueue_webfont() make the lower-level API, that doesn't mean we have to require theme authors to call those functions. Theme authors could specify directives in theme.json that would then be translate to calls to those functions. The functions would need to be available still, e.g. for plugin authors, who wouldn't have theme.json as a mechanism.

Imagine one entry into the Webfonts API that serves plugins, block themes with a theme.json file, and themes without that json file. By standardizing the schema of how themes and plugins tell Core about their webfonts, this single entry point could be achieved.

Let's think about by starting with the end in mind, i.e. theme.json.

The configuration of webfonts will part of the theme.json. Once the theme.json is parsed and the webfonts extracted from it, then it could be passed to the Web Fonts API for its processing. Imagine the webfonts part of the theme.json after it's parsed. Let's imagine it as an array. If that's the entry point into the API, then that same schema can be used as the input via a theme.json or directly passing the collection as an array to the API's registration function such as wp_register_webfont().

This could then lead to one API function for registration that handles registering webfonts from multi-sources including themes with a theme.json file, themes that directly invoke wp_register_webfont(), and plugins that directly invoke wp_register_webfont().

Let's see that in action.

This PR requires the theme or plugin to register / enqueue like this:

{{{php
Passing a URL as the src.
wp_enqueue_webfont( 'roboto', 'https://fonts.googleapis.com/css2?family=Roboto:ital@0;1&display=swap' );

wp_enqueue_webfont(

'my-font-normal-400',
,
array(

'font-family' => 'My Font',
'src' => array(

get_template_directory_uri() . '/fonts/font.woff2',
get_template_directory_uri() . '/fonts/font.woff',

),
'provider' => new WP_Fonts_Provider_Local(),

)

);
}}}

or like this:
{{{php
wp_enqueue_webfont(

'roboto-400',
,
array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '400',
'provider' => new WP_Fonts_Provider_Google(),

)

);

wp_enqueue_webfont(

'roboto-italic-400',
,
array(

'fontFamily' => 'Roboto',
'fontStyle' => 'italic',
'fontWeight' => '400',
'provider' => new WP_Fonts_Provider_Google(),

)

);

wp_enqueue_webfont(

'my-font-normal-400',
,
array(

'font-family' => 'My Font',
'src' => array(

get_template_directory_uri() . '/fonts/font.woff2',
get_template_directory_uri() . '/fonts/font.woff',

),
'provider' => new WP_Fonts_Provider_Local(),

)

);
}}}

With the new proposal, it can simplified to this:
{{{php
wp_register_webfont(

array(

'roboto-normal-400' => array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '400',
'provider' => 'google',

),
'roboto-italic-400' => array(

'fontFamily' => 'Roboto',
'fontStyle' => 'italic',
'fontWeight' => '400',
'provider' => 'google',

),
'my-font-normal-400' => array(

'fontFamily' => 'My Font',
'fontDisplay' => 'swap',
'fontStyle' => 'normal',
'fontWeight' => '400',
'src' => array(

get_template_directory_uri() . '/fonts/font.woff2',
get_template_directory_uri() . '/fonts/font.woff',

),
'provider' => 'local',

),

);
}}}

hellofromtonya commented on PR #1573:


3 years ago
#48

Notice in the last example how the array follows the theme.json schema. That's what I meant by standardizing the input to specify the webfonts collection schema.

felixarntz commented on PR #1573:


3 years ago
#49

Passing a URL as the src.
wp_enqueue_webfont( 'roboto', 'https://fonts.googleapis.com/css2?family=Roboto:ital@0;1&display=swap' );

To my above question, is that still relevant given the latest iteration supports font providers? I think it'd be more appropriate to register e.g. Google fonts by registering a font like in the other examples and specifying google. The example you have here with the URL would be just another way to accomplish that, which I'm not sure we want to allow for instead of the params approach which relies on structured data.

I agree with pretty much everything you're saying otherwise. Regarding your last example though (wp_register_webfont with an array to register multiple fonts), I'm still unsure why that's better than wp_register_webfont with a single font, like it is now. The theme.json input could as well translate into a foreach loop where then wp_register_webfont is called accordingly for each. Last but not least, if we decide to go with that array approach, the function should probably called wp_register_webfonts, since it wouldn't be just one webfont that is registered.

hellofromtonya commented on PR #1573:


3 years ago
#50

The example you have here with the URL would be just another way to accomplish that, which I'm not sure we want to allow for instead of the params approach which relies on structured data.

I agree with you. I think we want to standardize on the params structure and not an optional src or params.

Why?

It could make collecting the like fonts a little bit easier rather than parsing separate URLs that have been registered by a theme and/or plugin.

I'm still unsure why that's better than wp_register_webfont with a single font, like it is now. The theme.json input could as well translate into a foreach loop where then wp_register_webfont is called accordingly for each. Last but not least, if we decide to go with that array approach, the function should probably called wp_register_webfonts, since it wouldn't be just one webfont that is registered.

Why better? A couple of things come to my mind:

  • Supporting classic and block themes and/or migrating some day to theme.json. Converting from an array to JSON is fairly straightforward process (i.e. in the theme)
  • Potentially eliminating multiple iterators, i.e. foreach. How so? Imagine a case where a plugin and/or theme makes it DRY by using an array and then iterating through the webfont definition records to invoke the registration for each.

felixarntz commented on PR #1573:


3 years ago
#51

+1 to both of that. I'd be leaning towards having a higher-level wp_register_webfonts( $fonts ) which accepts an array of one or more webfont entries. That function would then iterate over them and call wp_register_webfont for each.

hellofromtonya commented on PR #1573:


3 years ago
#52

I'd be leaning towards having a higher-level wp_register_webfonts( $fonts ) which accepts an array of one or more webfont entries. That function would then iterate over them and call wp_register_webfont for each.

+1 Agreed!

I'll start a new branch on Ari's fork with this new approach and optimized architecture. Why? To retain this PoC PRs history and implementation.

Also plan to include a suite of unit/integration tests. Once ready, performance benchmarks will be needed too.

#53 @hellofromTonya
3 years ago

  • Keywords needs-patch needs-unit-tests added; has-patch has-unit-tests removed
  • Milestone changed from Awaiting Review to 5.9
  • Owner set to hellofromTonya
  • Status changed from new to assigned

@aristath and @jonoaldersonwp created a wonderful starting point with PR 1573. It served to foster discussions and discovery about what this API could be now and into the future. It caught my attention and imagination.

Learnings and a refined approach

During this process, thinking shifted to start with the end in mind, i.e. supporting theme.json for what that schema should be. Defining that schema led to thinking about a standardized way to configure one or more webfonts within a theme that has theme.json, a theme that doesn't, and a plugin.

  • How could that schema be standardized for all of these use cases?
  • Could the API have a single entry point for registration to support all of these use cases?
  • Could that schema be standardized so that extenders could migrate and support different types of products and themes?
  • Could that schema led to further performance boosts?

This exploration led to a refined approach:

  • A standardized "webfonts collection" schema that works in JSON and array format:

within theme.json

{
	"webfonts": {
		"roboto-normal-400": {
			"fontFamily": "Roboto",
			"fontWeight": "400",
			"fontStyle": "normal",
			"provider": "google"
		},
		"roboto-italic-400": {
			"fontFamily": "Roboto",
			"fontWeight": "400",
			"fontStyle": "italic",
			"provider": "google"
		},
		"my-font-normal-400": {
			"fontFamily": "My Font",
			"fontDisplay": "swap",
			"fontWeight": "400",
			"fontStyle": "normal",
			"src": [ 
				"fonts/MyLocalFont.woff",
				"fonts/MyLocalFont.woff2" 
			],
			"provider": "local"
		},
		"my-custom-font-normal-400": {
			"fontFamily": "My Custom Font",
			"fontWeight": "400",
			"fontStyle": "normal",
			"provider": "my-custom-provider"
		}
	}
}

and in an array:

wp_register_webfonts(
	array(
		'roboto-normal-400' => array(
			'fontFamily'  => 'Roboto',
			'fontStyle'   => 'normal',
			'fontWeight'  => '400',
			'provider'    => 'google',
		),
		'roboto-italic-400' => array(
			'fontFamily'  => 'Roboto',
			'fontStyle'   => 'italic',
			'fontWeight'  => '400',
			'provider'    => 'google',
		),
		'my-font-normal-400' => array(
			'fontFamily'  => 'My Font',
			'fontDisplay' => 'swap',
			'fontWeight'  => '400',
			'fontStyle'   => 'normal',
			'src'         => array(
				get_template_directory_uri() . '/fonts/font.woff2',
				get_template_directory_uri() . '/fonts/font.woff',
			),
			'provider'     => 'local',
		),
		'my-custom-font-normal-400' => array(
			'fontFamily'  => 'My Custom Font',
			'fontStyle'   => 'normal',
			'fontWeight'  => '400',
			'provider'    => 'my-custom-provider',
		),
	)
);
  • The API can handle bundling the providers, registering each, and instantiating each when needed
  • As @flixos90 suggested, the API can expose a way to register a custom provider, e.g. wp_register_webfont_provider()
  • Each provider is configured in the schema via its unique ID or slug
  • As @flixos90 suggested, the API can collect like fonts for optimizing processing and external fetching
  • Modern(ish) architecture can be built with:
    • OOP to encapsulate the processing, eliminating the need for private global functions and longer functions
    • schema validation
    • parameter input validation:
    • type hinting (where appropriate for the supported PHP versions)
    • type validation with doing it wrong (when type hinting is not an option)
    • PHP 8.0 and 8.1 compatibility
    • full unit/integration tests
    • performance testing

What about following the existing pattern for enqueue/register?

While attempting to reuse wp_enqueue_[script|style] patterns feels familiar, it created complexity and confusion. It's limiting what this API could do such as collecting like fonts to make one request to an external API for improved performance.

This is a new API. Starting with the future in mind, this API can be built today as a stepping stone to the future.

What's the next step?

The current plan is to create a scaffolding of the new architecture in a branch on @aristath fork. I'll start this tomorrow. Existing knowledge from the PoC will be ported into it. Everyone is welcomed and encouraged to collaborate.

With this refined approach and theme.json need for this, I'm moving it into the milestone. Thank you everyone who has been contributing to the discussion!

This ticket was mentioned in PR #1736 on WordPress/wordpress-develop by hellofromtonya.


3 years ago
#54

  • Keywords has-patch has-unit-tests added; needs-patch needs-unit-tests removed

Currently in development.

This implementation accepts an array of defined webfonts (aka "webfonts collection") using a defined schema.

  • It'll allow for like font family gathering for querying and optimized fetching/processing.
  • Instead of following the existing styles/scripts dependencies patterns of register and enqueue, this API accepts an array of webfonts and then handles its processing.

Trac ticket: https://core.trac.wordpress.org/ticket/46370

aristath commented on PR #1736:


3 years ago
#55

Styles for webfonts now get properly enqueued in the editor.
I created a draft PR in the twentytwentytwo theme implementing the webfonts API, we can use that to test the implementation 👍

azaozz commented on PR #1736:


3 years ago
#56

Been looking at this patch for couple of days and thinking it may be going a bit too far/in a wrong direction. Few things:

  1. As @hellofromtonya mentioned here: https://core.trac.wordpress.org/ticket/46370#comment:42 this seems very big and very complex, both as an API for use by themes and plugins and as code. Unfortunately it seems it has become even larger and more complex since then.

Currently a theme can add a webfont with about 40 lines of code. That code is typically copied and reused from one of the default themes, making it really simple. Looking at the requirements here, it seems quite more difficult at add a webfont by using the proposed API.

  1. Looking at the code there is a lot of validation going on, and is even separated in another subclass. What is the purpose of that validation/verification? What are the expected use cases that are covered by it? Are new webfonts going to be registered on every page load in production? Or are the webfonts settings going to be provided by the users (direct user input)?

Looking at expected use cases, this is an internal API not expected to be accessed from outside of WordPress like for example the REST API. The webfonts settings are pretty much going to be hard-coded in themes and plugins. So all the validation, including all the regex, etc. will be running many millions of times per day on these constant settings, completely redundantly. The only possible case where that validation might eventually be useful for a short time is while a developer is starting a new theme or plugin and doesn't follow the (still missing) documentation and examples. In all other cases it's just churn.

  1. As mentioned above documentation and examples need to be added, perhaps at the top of webfonts-api.php (assuming that's the expected way to use the API). There are some examples on the trac ticket, but they need to be in the docblock :)
  2. The whole structure seems overly complex, perhaps overengineered. There are several classes and subclasses that don't really do much apart from storing coupe of values and providing couple of methods. Perhaps simplifying it would make the code shorter/clearer/better/easier to read and understand. I understand this may be modeled after the WP_Dependencies classes and functions but the main purpose there is to calculate the load order of JS and CSS files, and to provide a way for enqueuing and outputting of inline scripts and styles before and after an enqueued file. Here the case seems quite simpler.
  3. There are two issues that are not addressed yet. We talked with @hellofromtonya few days ago about these, don't see them mentioned here yet.
    • User privacy concerns. Using a non-local webfont is pretty much the same like using a "pixel" or a third-party analytics service. In some regions, most notably in Europe, a website visitor may need to be asked to provide consent for their data being send to a third party. I understand that's not part of this API, but at least a proper (inline) documentation should be provided, explaining the implications to theme and plugin authors so they can take care of their users. This is necessary as WordPress will eventually have the Google webfonts URLs hard-coded as part of this patch.
    • Generated CSS. I may be wrong but some commercial webfonts come with quite restrictive licenses and usage directions, and seem to provide all necessary files and formats, thanks to @skorasaurus for the example. Could this CSS generation be seen as perhaps breaking of such licenses? Also why is this still necessary (more inline docs opportunities).

azaozz commented on PR #1736:


3 years ago
#57

Uh, sorry about the long comment above. For now I'm a bit worried that this is far too complex, not only as "code design" but also as usage. What would be the compelling reason a theme would prefer to switch to using this API rather than continue to hard-code loading of the web fonts it needs, like it has been doing for the last five years or so?

hellofromtonya commented on PR #1736:


3 years ago
#58

Currently a theme can add a webfont with about 40 lines of code. That code is typically copied and reused from one of the default themes, making it really simple. Looking at the requirements here, it seems quite more difficult at add a webfont by using the proposed API.

It's less code for themes and plugins.

How so?

Without this API, themes and plugins:

  • Register, enqueue, and inline styles for wp_enqueue_scripts and 'wp-block-library'
  • Generate and return @font-face CSS for the inline
  • And handle the preconnect link for externally hosted fonts

With this API, themes and plugins:

  • Define their webfonts in theme.json or in a PHP array passed to wp_register_webfonts()

### See this action 👀
Take a look at the new TT2 theme's PR 95:

  • Removes 52 lines of code
  • Adds 18 lines to define the webfonts collection (i.e. an array of arrays)
  • Adds 4 lines of PHP code to pass the array into Core

This PR is an example of how plugins and non-theme.json themes can simplify their code while gaining the benefits of the API.

Note: Once this API is committed, then theme.json parser can be updated. Then TT2's webfonts collection will move into its theme.json.

### What are benefits for theme and plugin authors and extenders? 🚀

  • Less code to write and maintain.
  • Their webfont collection (configurations) are portable. They can copy/paste between different themes, plugins, and projects. How so? The structure is standardized and agnostic.
  • The code is more readable and maintainable. How so? It uses CSS properties to define and configure the webfonts. No CSS to write. And very very little PHP.
  • As web performance evolves, their products / projects are no longer affected. Core handles it for them. Less time to be aware, update, and maintain changes in their existing products / projects.

hellofromtonya commented on PR #1736:


3 years ago
#59

What is the purpose of that validation/verification? What are the expected use cases that are covered by it? Are new webfonts going to be registered on every page load in production?

The webfonts settings are pretty much going to be hard-coded in themes and plugins. So all the validation, including all the regex, etc. will be running many millions of times per day on these constant settings, completely redundantly.

The validation code protects users and provides debug help for extenders.

For performance:

What's the purpose of it?

  • For required configuration parameters: validate the keys exist and the values are of the data type required. Why?
    • To guard Core and users against fatal errors of attempting to access a key that does not exist
    • To avoid unnecessary processing if those required parameters do not exist
    • To alert developers where schema issues exist to help them debug issues in their code
  • For optional configuration parameters: check if keys and values are valid, if no, set to a default. Why?
    • Avoids unnecessary processing such as making an invalid request to an external font service
    • Protects the user from delays, errors, FOUC, and/or unexpected styling issues

Or are the webfonts settings going to be provided by the users (direct user input)?

Yes that's possible.

hellofromtonya commented on PR #1736:


3 years ago
#60

The whole structure seems overly complex, perhaps overengineered. There are several classes and subclasses that don't really do much apart from storing coupe of values and providing couple of methods.

This architecture is designed to:

  • Simplify how extenders register webfonts
  • Standardize registering and handling of webfonts
  • Be agnostic to where the webfonts are defined (could be theme.json powered theme, a theme without a theme.json, a plugin, or interface where the user selects them)
  • Performance:
    • Fetch and process from external font services (one request instead of individual requests)
    • Add HTML and CSS including preconnect links
    • Orchestration of webfont collections to reduce additional sorting iterations
  • Extensible for extenders
  • Support font family and font pairing rendering in the block editor
  • Future enhanceable
  • Be robust (not fragile)
  • Be readable and understandable as code is (a) broken up into areas of responsibility and (b) with minimized coupling between the pieces

How so?

I hadn't yet detailed the architecture of what each piece does and why. Now is a good time. Let's start with the pieces that are needed and then move to have each class is delivering those pieces.

### The Pieces Needed

For the webfonts (WP_Webfonts_Registry):

  • A mechanism to register a webfont
  • An in-memory storage container to house the registered webfonts until it's time to process them
  • Ability to handle both font-family (kebab-case) and fontFamily (theme.json is in camelCase but the CSS needs kebab-case -> building in the ability to handle both types)
  • A way to pass the webfonts organized by font-family to their respective providers
  • A way to query the webfonts (i.e. get them for usage outside of the API)

For the providers (WP_Webfonts_Provider_Registry):

  • A mechanism to register a provider
  • An in-memory storage container to house the registered provider
  • A mechanism to instantiate a provider
  • The ability to receive its webfonts
  • A trigger to tell the provider to do its work on the given webfonts

For the API (WP_Webfonts_Controller):

  • A manager to orchestrate all the above
  • The glue between all of the pieces

For local or external fetching and CSS (the providers):

  • A mechanism to handle locally hosted web files and generate its @font-face
  • A mechanism to handle Google Fonts and generate its @font-face
  • The ability for custom providers to be registered to connect to other external font services

### The Registry

  • Pattern: Repository pattern
  • Common pattern in Core: WP_Hook, WP_Sitemaps_Registry, WP_Block_List, WP_Block_Pattern_Categories_Registry, WP_Block_Patterns_Registry, WP_Block_Styles_Registry, WP_Block_Type_Registry, WP_Styles, WP_Scripts, and more.

An in-memory storage mechanism is needed for the API to _collect_ and then _organize_ the optimizations to be done. Instead of register and processing each individual webfont, the goal is to organize the collections for each provider in such a way that each provider can do its work in an optimum way without duplicating the sorting process.

The knowledge of how to work with the webfonts or providers is contained within their respective registry. As each has an in-memory container to house the registered items, further optimizations can be done. These registries provide the mechanisms to handle the work of registering, preparing, verifying, and querying. That know-how is contained inside of each of these registries.

Benefits:

  • Know-how encapsulated inside of each registry: they know how to handle the work of registering, preparing, verifying, and querying. That know-how is contained inside of each of these registries.
  • Faster fetching from external font services. How so?
    • Fetch all of the webfonts in one request (instead of multiple requests).
  • Faster rendering. How so?
    • The block editor and/or plugins querying the webfonts get them in this pre-organized (sorted) structure.
  • Faster processing. How so?
    • One loop when registering the webfonts that handles all the work to prepare, check, sort, and then store each. This means other code doesn't need to do redundant work including the providers, block editor, and plugins.
    • The combination of the Controller and Registry means filtering is not needed (as filtering adds memory, CPU cycles, and the potential to alter or even break the schema). Instead, the registry exposes methods for getting and querying. No filtering needed.
  • Eliminates duplicates.
    • It's possible that a user adds a plugin to their site that is using the same webfont. With the registry, only one of the same type can be registered.
    • This prevents unnecessary work.
  • It's built for the future, i.e. to allow future enhancements. For example, there may be a need to remove a webfont (deregister).
  • It's testable

### The Providers
The provider is a processor that encapsulates the business logic of how to generate the @font-face CSS. It's built for future enhancements.

Benefits:

  • Extensibility: extenders can create their own custom providers to connect to other font services
  • Encapsulates the know-how for processing a specific type of webfont src
  • It's built for the future, i.e. to allow future enhancements
  • It's testable

### The Controller
Pattern: Controller
Common pattern in Core: heavily used in the REST API (for site healthy, taxonomies, templates, search, block patterns, users, widget types), sitemaps, and more.

The Controller is the glue. It's the manager directly the flow of work between the pieces and keeping the coupling between to a minimum.

Benefits:

  • Changing the processing order or hooks is readily found within it
  • The coupling between the objects is minimized
  • It's straightforward to trace through what gets called and when
  • It's built for the future, i.e. to allow future enhancements
  • It's testable

hellofromtonya commented on PR #1736:


3 years ago
#61

Perhaps simplifying it would make the code shorter/clearer/better/easier to read and understand. ...Here the case seems quite simpler.

It's doing a lot than those highly specific 40 lines of code that were originally in a theme. (See the details above.)

@azaozz do you still feel it's overengineered? If yes, what ideas do you have to simplify it and still deliver everything it needs to do?

aristath commented on PR #1736:


3 years ago
#62

User privacy concerns. Using a non-local webfont is pretty much the same like using a "pixel" or a third-party analytics service. In some regions, most notably in Europe, a website visitor may need to be asked to provide consent for their data being send to a third party. I understand that's not part of this API, but at least a proper (inline) documentation should be provided, explaining the implications to theme and plugin authors so they can take care of their users. This is necessary as WordPress will eventually have the Google webfonts URLs hard-coded as part of this patch.

This is not the end of the implementation, it is the beginning. Before we discuss privacy concerns, loading remote files etc, we need to have a base we can build on and improve.
In the previous implementation/POC we already had a method to grab the remote files and host them locally. This will improve both privacy and performance, but we chose not to include such functionality in this PR, and instead do a followup patch to add it, once this gets merged.
Strictly speaking, it is not exactly part of the API definition, and further discussion on it will probably be needed. Discussions about this behavior have the potential to torpedo and delay merging the initial implementation of the API - and so it was split off the initial PR and will be a separate followup.
For reference, the code in the previous POC that downloads the font-files and stores them locally can be seen here: https://github.com/WordPress/wordpress-develop/blob/381ae55c6f4ef72cd3be4987019f7316203958c2/src/wp-includes/class.wp-webfonts.php#L108-L249

aristath commented on PR #1736:


3 years ago
#63

Generated CSS. I may be wrong but some commercial webfonts come with quite restrictive licenses and usage directions, and seem to provide all necessary files and formats, thanks to @skorasaurus for the example. Could this CSS generation be seen as perhaps breaking of such licenses? Also why is this still necessary (more inline docs opportunities).

Users have the freedom to use this API however they want... If the user downloads non-free webfont files, then bundles them in their theme and registers them using this API, the CSS we generate is not an issue. The issue is the font-files they used.

aristath commented on PR #1736:


3 years ago
#64

Generated CSS. I may be wrong but some commercial webfonts come with quite restrictive licenses and usage directions, and seem to provide all necessary files and formats, thanks to @skorasaurus for the example. Could this CSS generation be seen as perhaps breaking of such licenses? Also why is this still necessary (more inline docs opportunities).

Users have the freedom to use this API however they want... If the user downloads non-free webfont files, then bundles them in their theme and registers them using this API, the CSS we generate is not an issue. The issue is the font-files they used.

aristath commented on PR #1573:


3 years ago
#65

Closing this PR as it has been replaced by #1736

#66 @aristath
3 years ago

Testing instructions for the new patch (https://github.com/WordPress/wordpress-develop/pull/1736):

Registering a webont (or collection of webfonts is easy using the wp_register_webfonts function.
The code example below registers a variety of webfonts, both from local files and google-fonts:

<?php
/**
 * Plugin Name: WebFonts Test
 */

add_action( 'init', function() {
        if ( ! function_exists( 'wp_register_webfonts' ) ) {
                return;
        }
        wp_register_webfonts(
                array(
                        array(
                                'fontFamily' => 'Roboto',
                                'fontStyle'  => 'normal',
                                'fontWeight' => '400',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily' => 'Roboto',
                                'fontStyle'  => 'italic',
                                'fontWeight' => '400',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily' => 'Open Sans',
                                'fontWeight' => '100',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily' => 'Festive',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily' => 'Open Sans',
                                'fontWeight' => '300',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily' => 'Open Sans',
                                'fontWeight' => '400',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily' => 'Open Sans',
                                'fontWeight' => '800',
                                'provider'   => 'google',
                                'isExternal' => true,
                        ),
                )
        );

        wp_register_webfonts(
                array(
                        array(
                                'fontFamily' => 'Akronim',
                                'provider'    => 'google',
                                'isExternal' => true,
                        ),
                        array(
                                'fontFamily'   => 'My Font',
                                'font-display' => 'swap',
                                'fontStyle'    => 'normal',
                                'fontWeight'   => '400',
                                'src'          => array(
                                        get_template_directory_uri() . '/fonts/font.woff2',
                                        get_template_directory_uri() . '/fonts/font.woff',
                                ),
                                'provider'     => 'local',

                        ),
                )
        );

        wp_register_webfonts(
                array(
                        array(
                                'font-family'  => 'Source Serif Pro',
                                'font-weight'  => '200 900',
                                'font-style'   => 'normal',
                                'font-stretch' => 'normal',
                                'src'          => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ),
                                'provider'     => 'local',
                        ),
                        array(
                                'font-family'  => 'Source Serif Pro',
                                'font-weight'  => '200 900',
                                'font-style'   => 'italic',
                                'font-stretch' => 'normal',
                                'src'          => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2' ),
                                'provider'     => 'local',
                        ),
                )
        );
}, 20 );

The code-snippet from above adds 5 google fonts and 2 locally-hosted webfonts. It tests both using camelCase and kebab-case in the array keys.
The result is @font-face rules generated and added inline on the frontend, AND the editor. The font-families can be used directly in elements, and to test that the easiest method is to edit the styles of an element using the browser's inspector and add font-family: Festive. Both the editor side and the frontend side need to be verified.

Last edited 3 years ago by aristath (previous) (diff)

aristath commented on PR #1736:


3 years ago
#67

Testing instructions for webfonts registration added in https://core.trac.wordpress.org/ticket/46370#comment:66

aristath commented on PR #1736:


3 years ago
#68

@felixarntz Since you initially reviewed/added comments on the previous PR on https://github.com/WordPress/wordpress-develop/pull/1573, could you take a look at this one?
This is an updated/refactored API for webfonts, allowing users to register collections of webfonts instead of forcing them to register webfonts one by one. It addresses all concerns that were brought up in the initial implementation (inconsistent methods, multiple google-fonts requests etc).
In this patch, all webfonts are collected by provider, and then we make a single request to get the webfonts (as opposed to 1 request per webfont registered). It is more performant and has a proper, modern structure.
The schema is consistent and can be used for all providers, both in PHP and theme.json.

azaozz commented on PR #1736:


3 years ago
#69

Wow, a lot of explanations in response to https://github.com/WordPress/wordpress-develop/pull/1736#issuecomment-946028976, thanks. Lets try to quickly look through them.

It's less code for themes and plugins.
...
See this action 👀

Take a look at the new TT2 theme's PR 95:

Reduces the theme's webfont code by ~42%:

Hmm, think this is incorrect and my point is missed here. Without using this patch a theme would add the two fonts from the above PR with 38 lines of code (including docblock, comments, etc.). With this patch it will add pretty much the same things, but in a different format, in 20 lines of code. However for that to actually work it also needs over 2000 lines of code (3000 including the tests) of "helpers" from WP.

Still think that this patch contains a very large amount of code and is made in a very complex and complicated way. I don't really see why it has to be that complex. I understand, it's easier to write more complex, "overengineered" code. It's a lot harder to write simple, easy to read and easy to understand, up-to-the-point code :)

What are benefits for theme and plugin authors and extenders? 🚀

Less code to write and maintain.

Not really. The amount is roughly the same and the code is copied/pasted. The only difference is that the format is a bit different now: a multidimensional array containing "CSS like" settings instead outputting the actual settings directly.

Their webfont collection (configurations) are portable. They can copy/paste between different themes, plugins, and projects. How so? The structure is standardized and agnostic.

Again, not really. The previous code can be copied and pasted between themes and would work just as well everywhere. Then, if needed, adjustments can easily be made directly in the CSS.

The code is more readable and maintainable. How so? It uses CSS properties to define and configure the webfonts. No CSS to write. And very very little PHP.

Again, not a benefit of the patch. As it requires the settings (as previously used or provided by third parties) to be "converted" to an array which is mostly undocumented at the moment (more about that later). This seems to be harder especially for theme authors that are more familiar with CSS than PHP, and is actually a weakness.

As web performance evolves, their products / projects are no longer affected. Core handles it for them. Less time to be aware, update, and maintain changes in their existing products / projects.

Right this may be a benefit in the future, eventually. Web fonts have been around for quite a few years so not expecting any "revolutionary" changes to how they work, however WP core may try to fix "bad practices" at some point.

On the other hand there seem to be some "questionable" things in this patch like for example perhaps preloading a font file that is outputted just couple of lines below the preload code. Is this a good idea/needed? Seems like not needed.

azaozz commented on PR #1736:


3 years ago
#70

Lets continue :)

The validation code protects users and provides debug help for extenders.

This is only partially true. It may provide a little bit better error messages for extenders but only when they are starting to develop a new theme or plugin. This is an extremely rare case considering how many millions of times that code will be running every day. Furthermore, even in that case the error messages will only be slightly better. If there was no validation, the standard PH warnings/errors would be triggered.

In addition, the validation code does not protect users (in production) in any practical way. This is an internal-use-only, low-level mini-API. It is not like the REST API for example. In functionality it is pretty much the same as the script loader, only it is (should be) a lot simpler as there are no dependencies here. In that terms this validation code is completely redundant in production, and should not be running.

Perhaps I'm wrong. Perhaps a theme or a plugin can be written in such a bad way as to break that low-level mini-API in some rare cases. However looking at the only example usage for now (as mentioned above), I don't see how would that be possible.

IMHO perhaps the biggest problem with this patch at the moment is that it is mostly theoretical, not based on practical needs/use cases. It would be great to demonstrate the need of validating the same constant settings arrays over and over again millions or perhaps billions of times every day by trying to make an example plugin, perhaps.

For performance:

It only runs when a webfont is registered.

Right. Looking at how many themes currently use webfonts, that would probably be several times per front-end page load. Think there were some estimated numbers of total page loads in all of the WP installs per day, was well over a billion if I remember right.

It's super duper fast about < 5 microseconds for it to validate a webfont (See the profiler in action here.).

Not sure about that. Having several regex instances generally slows down PHP quite a bit the first time the regex code is used, even if the strings are short and the regex is well written. The reason it that PHP uses an external library to process these, and it takes some time to initialize it (afaik). So the performance impact may not be huge, but will definitely be noticeable.

There is no negative impact on performance.

Hmm, don't think so. Even if the slow-down is small, multiply it by all the page loads per day. The numbers are hude :)

azaozz commented on PR #1736:


3 years ago
#71

Hmm, we can continue this back-and-forth for a while but thinking it's not particularly productive.

For now I think there are two blockers:

  1. Privacy issues because WordPress will include external URLs that reveal user information. Frankly I'm not sure what the best fix would be here. Looking at https://github.com/WordPress/wordpress-develop/pull/1736#issuecomment-946388271

In the previous implementation/POC we already had a method to grab the remote files and host them locally.

Perhaps this would be a good idea however not sure if some font licenses would allow such "caches". Another option would be to ask the users and the site visitors for their consent before loading any externally hosted fonts. In any case thinking this needs to be resolved before the part of the patch that introduces external URLs in core is committed.

  1. Documentation and usage examples. There are so many explanations of how things work or are supposed to work on this ticket now, think refining them a bit (and shortening them of course) would make a pretty good inline documentation for it. IMHO it is imperative that there are plenty of docs and examples included in the source as usage is quite complex, especially around using or setting or resetting font providers.

azaozz commented on PR #1736:


3 years ago
#72

Hmm, looking a bit more, why the need to have "font providers" as separate setting? A web font is defined by two things: its URL and the small bit of CSS that tells the browser how to use it. The "provider" is part of the URL. Why the need to split it off?

It seem a lot of complexity comes from separating the "providers". This also introduces many currently unhanded edge cases. For example:

  1. Plugin A registers provider X and enqueues fonts 1 and 2.
  2. Plugin B de-registers provider X and registers provider Y.

What happens with fonts 1 and 2? They seem to get dropped perhaps (undocumented).

  1. Plugin B continues and registers fonts 1, 3 and 4 where font 1 is almost identical to font 1 that was registered by plugin A.

What happen now? Font 2 is still missing and a substitute will be used, would font 1 work in the cases intended by plugin A or would plugin A fail completely?

There are several other edge cases which are not necessarily useful but theoretically possible. Given the size of WP, plugins and themes will eventually trigger them.

Thinking that it's perhaps not the best idea to split the providers from the font URLs and require plugins and themes to set/reset/unset them. Seems these providers can be handled "internally" by the API if it's needed at all. This would correspond to how scripts and stylesheets are currently handled/loaded, and will decrease the complexity a lot.

In addition it looks like all "provider settings" can be extended much clearer/easier by using just a single filter instead of having to extend a class, which generally is harder to write and maintain, and eventually will bring more back-compat considerations in the future.

felixarntz commented on PR #1736:


3 years ago
#73

@azaozz Trying to address some of your concerns:

Without using this patch a theme would add the two fonts from the above PR with 38 lines of code (including docblock, comments, etc.). With this patch it will add pretty much the same things, but in a different format, in 20 lines of code. However for that to actually work it also needs over 2000 lines of code (3000 including the tests) of "helpers" from WP.

Based on the assumption that these numbers are accurate, let's look at what this would mean:

  • Let's say 100 plugins use web fonts. Without this API, this means 3,800 (100*38) lines of code. With this API, this means 4,000 (100*20+2,000) lines of code.
  • Let's say 200 plugins use web fonts. Without this API, this means 7,600 (200*38) lines of code. With this API, this means 6,000 (200*20+2,000) lines of code.
  • Let's say 300 plugins use web fonts. Without this API, this means 11,400 (300*38) lines of code. With this API, this means 8,000 (300*20+2,000) lines of code.

Per this (of course very simplified) calculation, the number of lines written significantly decreases with this API the more plugins and themes there are. Since there are _way_ more plugins and themes than all of the numbers above (and probably also more than that use web fonts), we can definitely expect a reduction of code overall.

That said, the amount of code is not the only aspect to consider here. This API provides a central layer of registration and includes optimizations (outlined in previous comments) that probably wouldn't be considered by most plugin and theme developers or would require a more significant effort on their end, requiring them to write way more than the 38 or 20 lines of code we're speaking about here.

The code is more readable and maintainable. How so? It uses CSS properties to define and configure the webfonts. No CSS to write. And very very little PHP.

Again, not a benefit of the patch. As it requires the settings (as previously used or provided by third parties) to be "converted" to an array which is mostly undocumented at the moment (more about that later). This seems to be harder especially for theme authors that are more familiar with CSS than PHP, and is actually a weakness.

The high-level API function that PHP developers would be expected to use is wp_register_webfont() which works very similar to wp_register_style() and wp_register_script(), so this shouldn't be too much new. For theme authors the same applies, although it will eventually be even simpler for them, since a follow-up effort to introducing this foundational API is to use it automatically for a theme.json configuration. In other words, theme authors will be able to define their fonts in theme.json and then won't need to write a single line of PHP at all. I _do_ agree with your assessment that the API code needs to be better documented, I've also pointed that out in my code review above.

Privacy issues because WordPress will include external URLs that reveal user information. Frankly I'm not sure what the best fix would be here.

I'm not sure about this concern. From the default behavior, there are no privacy implications, they would only become relevant _if_ a theme/plugin developer decides to use one of the external font providers. Doing that is subject to the same privacy concerns as existing WP core features like oEmbed though, so I'm not sure this should be a blocker. Anyway, if the privacy issues with third-party fonts are a concern here, we _could_ potentially start the initial implementation without any font providers out-of-the-box.

It seem a lot of complexity comes from separating the "providers". This also introduces many currently unhanded edge cases. For example:

  1. Plugin A registers provider X and enqueues fonts 1 and 2.
  2. Plugin B de-registers provider X and registers provider Y.

What happens with fonts 1 and 2? They seem to get dropped perhaps (undocumented).

The API doesn't allow unregistering a provider, so this wouldn't be possible, as far as I can tell.

jono-alderson commented on PR #1736:


3 years ago
#74

@azaozz hoping to address a few more of your broader concerns around performance and necessity, and to revisit some key points which might have been overlooked in the thread above.

Firstly, let's not forget our broad objectives here.

WordPress is demonstrably _slow_, as compared to other platforms. Over the last few years, Wix, Shopify, Squarespace, Duda, Drupal and others have invested considerable focus and resources on improving the speed of their software for end users. If we want site owners to choose WordPress, they need them to be confident that they're not irritating their readers, frustrating their potential customers, and harming their bottom line.

At the moment, web fonts have a significant impact on the end-user experience of many WordPress sites. If we want to 'fix' WordPress' speed, we _have_ to find ways to improve how we load fonts. To fail to do so threatens our market share.

To some of your specific concerns:

"Web fonts have been around for quite a few years so not expecting any "revolutionary" changes to how they work."

The landscape has changed dramatically over the last few years, and we've not adapted to that (where others have).

In the last couple of years, we've seen a huge increase in focus on the performance impact of web fonts across the web. That's heavily influenced by Google's Web Vitals ecosystem, but there's also a broader 'front-end performance optimization' revolution underway. Web fonts are common offenders when it comes to performance bottlenecks for end consumers, often in regards to:

  • The number of HTTP requests
  • The # bytes transferred
  • Milliseconds of render-blocking, and/or FOUT/FOIT (and similar) periods
  • Impact on (cumulative) layout shift metrics
  • Carbon costs

We've also seen considerable evolution in the last few years around what 'best practice' looks like for defining, loading, and managing web fonts. For example:

  • Separation of character sets
  • Tactical use of font-display strategies (in line with preloading strategies; and, _already_, some changes in best practice here to no longer recommend swap by default).
  • Support for _variable fonts_
  • Glyph optimization
  • Deprecation of SVG fonts

There's also an increased focus on the privacy implications of cross-domain resource loading in general, and font foundries specifically. We want to move away from relying on loading resources from third-party domains _by default_, and web fonts are a big offender here, too; because of:

  • IP leakage
  • Browser capabilities sniffing / fingerprinting.

This API doesn't fix either of these areas overnight. But it _does_ provide us with a foundation to _start_ to address them - which we cannot do without it.

For all the same reasons that we need a framework for enqueuing scripts and styles in order to be able to, e.g., optimize their behaviour, ordering, output and conditionality, we need a framework for enqueuing fonts. That's unavoidably more complex than enqueuing scripts, as the nature of the ecosystem relies on _foundries_, _families_ and _variants_ which must all be managed; as opposed to 'plug and play' resources.

End theme and plugin developers, and end-users, cannot be reasonably expected to implement custom fonts in a performant manner 'manually'. We also can't expect them to update and refine their methodologies and syntax(es) as this ecosystem continues to evolve. That _we ourselves_ are running slow, outdated, render-blocking, eternally-hosted party Google Fonts code in the <head> of every template on wordpress.org (and can't resource addressing that) is testament to this. Hoping that users 'get it right' cannot be our strategy for keeping up with Wix.

Failing to provide tooling to support this means reliance on (plugins that use) brute-force output buffering, which is far more of a performance and stability concern than any of the milliseconds that our framework 'costs'. It also means relying on site owners to take proactive action; to _know they have a performance problem_, be sufficiently resourced and educated to research it, and manage selecting and implementing a solution. That web fonts are, and continue to be problematic for us, demonstrates how unrealistic that approach is.

The long-term roadmap that we want to build on top of this isn't yet specific, but without question it includes:

  • Localizing external fonts
  • Generating optimal CSS
  • Character set & glyph management

We can't do any of that until we have a way to abstract beyond a 'font' being implemented via arbitrary HTML which loads a CSS resource.

"On the other hand there seem to be some "questionable" things in this patch like for example perhaps preloading a font file that is outputted just couple of lines below the preload code. Is this a good idea/needed? Seems like not needed."

Having preloading support is important (regardless of the proximity of the resource reference and the preload tag), because:

  • It alters the browser's internal priority heuristics, which can affect delivery when the order of resource discovery and the presence of other resource requests might otherwise delay the delivery of in-flight resources, or delay takeoff.
    • NB, there's a WIP spec for managing priority as an explicit resource hint, but this isn't well-supported yet. If it gains traction, this may be a better mechanism. In which case, we'll need some abstraction in place to manage that!
  • It's desirable when used in conjunction with an intentionally blocking font-display strategy. E.g., if I can be 'sure' that a font will be delivered in a timely manner, I may wish to use an option with a different block/swap trade-off.

"Perhaps this would be a good idea however not sure if some font licenses would allow such "caches""

Most foundries do. Google Fonts does. Foundries with particularly restrictive or archaic implementation mechanisms (e.g., see TypeKit) won't be well-suited for this API regardless, as they rely on various JavaScript and authentication mechanisms (and often come with their own plugins).

More broadly, nothing currently prevents developers from mis-using / ignoring licensing or implementation constraints of custom fonts. This API doesn't provide or signpost any routes to misuse which weren't already present.

"Even if the slow-down is small, multiply it by all the page loads per day"

The milliseconds we're introducing are absolutely trivial in comparison to the many _seconds_ that we add to the loading time of each page that every end-user loads. It's even more trivial when compared to the carbon cost of the bytes transferred to-and-from requests to external font foundries.

aristath commented on PR #1736:


3 years ago
#75

The concerns voiced above are in the following areas:

  • Amount of code for implementing a webfont
  • Performance
  • Privacy
  • Documentation/Usage
  • Complexity from providers

I'll try to address them one by one:

## Concerns

### Amount of code:

Yes, in some cases a theme will need to include some extra lines of code. Registering a font with an API will take more likes than doing wp_enqueue_style( 'https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,700;1,400&family=Neuton:ital,wght@0,300;0,400;0,700;1,400&display=swap' );
However, if you try to do that, you'll quickly find out that it doesn't work. The stylesheets API does things differently and the URL above is an example why using the existing APIs is a bad idea... (see #51552, #49742). Yes, we can fix those tickets but that's just a random example, it's the tip of the iceberg.
Additionally, I fail to see how the number of lines written is of any consequence.
As someone who has worked for theme shops I've had to write and implement in-house webfonts implementations many times. Themes have options where users can select a font for their headers, content, other elements, users can upload fonts and so on. The result is that themes already include their own versions of a "webfonts API", and each one of them has to re-invent the wheel. Needless to say the results are inconsistent, in many cases perform poorly, and ultimately contain a lot more code than they would if they were using a core API.

### Performance

The performance gains on the frontend far outweigh any server-side costs.
I'll use Google-Fonts as an example here, as it's more complex than bundled fonts.
When not using the API (so just using wp_enqueue_style( $url ): the browser needs to get the URL which is on domain A. Then it reads the contents of that file and gets the font-files from domain B, and finally prints them.
When using the API: The server gets the contents of the remote URL, caches them, and then prints the CSS. The result is that the browser only needs to get the font-files themselves. That saves the browser a lot of extra round-trips and TLS handshakes. The additional of preload further improves the performance. Yes, it may only improve it by a few milliseconds but that's what the server-side cost is anyway.
As a whole, the webfonts API improves performance for visitors - and that is far more precious than 3-5ms spent server-side.

### Privacy

It is not the responsibility of this API to disclose to users that the website they visit may use external assets. That responsibility falls to themes/plugins. The API doesn't choose to use a google-font, the theme/plugin authors do. If they choose to use an external webfont, then any disclosure needed falls to them.
If anything, this API greatly improves privacy because the CSS is cached server-side and there are fewer requests to remote URLs, strengthening privacy. It's certainly a lot better than the current "wild-west" situation.
Having an API like this also opens the door for more future improvements like the one mentioned in my previous comment to further improve performance & privacy.

### Documentation/Usage

That is not a blocker... Documentation and usage examples is something we'll definitely need to write more about, but those should live in the actual documentation pages for the API functions on developer.w.org - and that can't happen before this gets merged in core.

For the purposes of testing the API I posted a usage example in the track ticket a few days ago.

### Complexity from providers

As mentioned above by @felixarntz, providers can't be unregistered so the scenarios mentioned are not really an issue.

Code-wise It's true that providers increase the complexity of the implementation, but that complexity also improves the extensibility of the API. Any future provider will be able to create their own class (which is pretty simple to do), and provide users with more options.


## Why do we need this API in WordPress?

This API brings a lot of value to WordPress

If theme/plugin developers printed scripts & styles directly in HTML, WordPress wouldn't be able to optimize & manage any of that. The same thing applies to webfonts. Currently, developers add webfonts in a dozen different ways, reinvent the wheel when they need something more complex, and we don't have access to any of that. We can't optimize the way webfonts are delivered. If 2 plugins add the same webfont, it's currently printed twice. The current state of webfonts in WordPress is horrible. This API brings consistency to an otherwise chaotic situation, with improvements across the board.
It significantly improves performance for visitors. There is an almost negligible server-side cost, in exchange for far more impactful frontend gains.
It improves privacy by reducing the number of requests to 3rd-party sites.

It will allow us to further iterate and further improve performance & privacy in future patches. But those improvements don't belong in the initial patch... That has always been the WordPress way of implementing new features: Start small and grow as you go.

Also important: future-proofing. We're headed full-speed towards a block-themes era. We need an API for webfonts. We need to be able to define webfonts in theme.json files. We need to be able to get these fonts consistency. We need to show them both on the editor-side, in previews, and the frontend. We need an API in order to do these things, and this patch is the first step towards that goal. There are proof-of-concept draft PRs already in Gutenberg and the TT2 theme using this implementation so we know for a fact that it's something we can use.

This is a solid start. It improves current behaviors, improves performance, strengthens privacy, doesn't touch any other part of core and there is absolutely zero chance of this causing any issues whatsoever on existing implementations. Here we're introducing the API. We're building something solid we'll be able to extend and improve in the future.

The code is sound. It is well-tested. I would have done it differently (then again, any dev will tell you the same thing), but that doesn't mean I would have done it better.

hellofromtonya commented on PR #1736:


3 years ago
#76

Multiple font-weights for a Google font doesn't work:

Hello @zaguiini, that's a great question and observation. Currently, the API does not handle a range of font-weight variations for building the URL endpoint for Google Fonts API. It assumed those font-weights would be specified individually as separate webfonts like this:

{{{php
wp_register_webfonts(

array(

array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '400',
'provider' => 'google',

),
array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '500',
'provider' => 'google',

),
array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '600',
'provider' => 'google',

),
array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '700',
'provider' => 'google',

),

)

);
}}}

I think your proposal to specify a range of font weights is interesting and likely valuable for theme and plugin authors to avoid adding more code. @aristath What do you think?

hellofromtonya commented on PR #1736:


3 years ago
#77

In talking with @azaozz today, he proposed the following 2 action items to move this API forward to ship with WP 5.9:

  • Action Item 1: Test and show the API working with plugins and a theme.

Create 2 plugins and a theme that each registers webfonts. Document how to register webfonts. Test for edge cases.

  • Action 2: Privacy. By default, the API does not fetch from external font APIs.

Make fetching from an external font service opt-in.
Let the theme and plugins tell the API that it's okay to do the fetch.

cc @aristath @jono-alderson @felixarntz

jeyip commented on PR #1736:


3 years ago
#78

I'm getting provider: local added to the @font-face declaration:

@zaguiini Not sure if you're calling this out as a potential issue, but I think the provider: local attribute is expected if you're using Ari's test data. Example:

https://i0.wp.com/user-images.githubusercontent.com/5414230/140214113-aaa64842-caef-496d-880f-27694f5085b1.png

zaguiini commented on PR #1736:


3 years ago
#79

I'm getting provider: local added to the @font-face declaration:

@zaguiini Not sure if you're calling this out as a potential issue, but I think the provider: local attribute is expected if you're using Ari's test data. Example:

https://i0.wp.com/user-images.githubusercontent.com/5414230/140214113-aaa64842-caef-496d-880f-27694f5085b1.png

Thanks @jeyip! I'm talking about the CSS output! Yes, this field is expected when registering the webfont, but it shouldn't show up on the CSS that's spit out in the browser.

aristath commented on PR #1736:


3 years ago
#80

I'm getting provider: local added to the @font-face declaration:

Thank you for reporting this!
Fixed in https://github.com/WordPress/wordpress-develop/pull/1736/commits/77d0efe53032741a8497b01248503b3d1d87c32f 👍

aristath commented on PR #1736:


3 years ago
#81

Multiple font-weights for a Google font doesn't work

That's a very good point! Fixed in https://github.com/WordPress/wordpress-develop/pull/1736/commits/1c49316ca5bc29ec98609d4315148edc56d7b2f0

You can now define a range of font-weights and they will get properly loaded.
Example:
{{{php
wp_register_webfonts(

array(

array(

'fontFamily' => 'Roboto',
'fontStyle' => 'normal',
'fontWeight' => '100 900',
'provider' => 'google',

),

)

);
}}}
will now result to this: https://fonts.googleapis.com/css2?family=Roboto:wght@100;200;300;400;500;600;700;800;900&display=fallback

aristath commented on PR #1736:


3 years ago
#82

Action Item 1: Test and show the API working with plugins and a theme.
Create 2 plugins and a theme that each registers webfonts. Document how to register webfonts (in the plugins/theme). Test for edge cases. Publicly share these test plugins/theme in a public repo for others to test it too.

aristath commented on PR #1736:


3 years ago
#83

Action 2: Privacy. By default, the API does not fetch from external font APIs.
Make fetching from an external font service opt-in.
Let the theme and plugins tell the API that it's okay to do the fetch.

Done in https://github.com/WordPress/wordpress-develop/pull/1736/commits/97bf2961122387ff318d942a0540bfd6cb99668a

In order to load google-fonts (or any other external API), users will now need to define 'is-external' => 'true' for the webfont.

zaguiini commented on PR #1736:


3 years ago
#84

How about 👇

Regarding the schema, what if we used an array instead of a space-separated string on the fontWeight property when dealing with multiple values? Then we can untie context-based behavior because a string means to be a single value instead of multiple ones.

It's also not clear that we are specifying a range and not a subset. Google Fonts accepts a subset of all possible weights when passing the argument, not an interval.

aristath commented on PR #1736:


3 years ago
#85

Regarding the schema, what if we used an array instead of a space-separated string on the fontWeight property when dealing with multiple values? Then we can untie context-based behavior because a string means to be a single value instead of multiple ones.

It's also not clear that we are specifying a range and not a subset. Google Fonts accepts a subset of all possible weights when passing the argument, not an interval.

We stay as close as possible to the default @font-face spec: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face

A font-weight value. Accepts two values to specify a range that is supported by a font-face, for example font-weight: 100 400.

"As close as possible" in this case means that we're trying to keep things consistent, and this should be a string.
If we want to add multiple values, then it's wiser (though it will take a few more lines to write) to define them separately. If we want to use a range, then 2 space-separated values make sense.
The code I pushed for this tweak, _does_ allow for multiple values separated by a space... So if you use 400 700 it will be a range, but 100 400 900 will load those 3. However, that was a happy accident and not the spec. In order to avoid overcomplicating the code - and considering this side-effect doesn't have a negative impact, I decided to leave in to make life easier. Should we guard against that easter-egg?

azaozz commented on PR #1736:


3 years ago
#86

Re-checking the two "blockers" that were identified about week ago.

  • Handling/resolving the site visitor and user privacy issues when loading fonts from external APIs is still pending. Think there's an understanding what can be done here for it.
  • The documentation has been improved but thinking quite a bit more is needed. Left some inline comments but they would probably be better here.

Still thinking a lot more documentation is needed right in the docblocks. I see some more brief descriptions were added to several functions, but some still need quite a bit more. Another important thing that's missing is explanations of how this works and how to use it, and examples (yes, right in the docblocks).

As this is helper functionality for use by themes and plugins, and is not used in core, it is very impotent to document it well, including inline comments in the code, and provide examples in the docblocks.

For example: thinking that for the wp_register_webfont() and wp_register_webfonts() functions at least the following is needed:

  • What are the expected settings, in details and with examples.
  • How is it supposed to work.
  • How are plugins supposed to cooperate when registering same or similar webfonts, i.e. do they first check if the font they are about to register (a.k.a. enqueue) has already been registered/enqueued by the theme or another plugin. What if the registration options are a bit different (can all of them be compared?). What happens if the same webfont is registered but with a different "provider"? Etc.

Thinking that all of these answers have to be right here in the docblock in a bit more concise form, and expanded in the documentation and the "announcement" post on make/core.

Same for wp_register_webfont():

  • What are webfont providers?
  • Why do they exist?
  • Why are they "registered" separately from the webfonts, what are the benefits of that and what are the drawbacks (concise).
  • How to register a "webfont provider" with examples.
  • How to cooperate with other themes/plugins that also register a provider and what pitfalls to avoid and how.
  • What is expected behavior and what is not, also best practices, etc.

This will help both the WordPress extenders and future maintainers to understand how this is supposed to work and why.

In addition thinking that it would be really good to add some explanations at the beginning of each class, not just say Webfonts API: [whatever] class.. The docs there should include a brief description of the class, what does it do, why is it needed, and how it is supposed to work. There are several places in WP where that's done and seems very helpful for new or beginner contributors. I'd expect that to become a standard practice in the future.

azaozz commented on PR #1736:


3 years ago
#87

Looking again at WP_Webfonts_Schema_Validator, still don't see the point of having it at this place. Would be great if its existence is confirmed by providing some examples of how it is supposed to work, and when.

The "Webfonts API" is a lower level, internal-use-only (mini?) API that is supposed to help with enqueuing if webfonts and should output the needed HTML and CSS for it. It is not a separate module that can be reused elsewhere (WP doesn't have modules in that sense).

Frankly I've never seen an internal, low level API to have a "schema" and "validation" of that schema. That schema may be needed in the upper level functionality that uses the lower level API as the upper level "knows" when validation may be needed and when is it pointless. For example see how that works in the REST API.

For the lower level functionality WordPress has a well established way to set defaults when an array is used as function parameter: wp_parse_args() https://developer.wordpress.org/reference/functions/wp_parse_args/. It is used pretty much everywhere in the same case as in this patch. Frankly I don't see why it is not used here.

I understand, the initial intention when adding the validator was to be helpful to WordPress extenders, but the result is that there are several regex calls that run continuously on every page load in production for every font that is registered and do not do anything useful.

zaguiini commented on PR #1736:


3 years ago
#88

Regarding the schema, what if we used an array instead of a space-separated string on the fontWeight property when dealing with multiple values? Then we can untie context-based behavior because a string means to be a single value instead of multiple ones.

It's also not clear that we are specifying a range and not a subset. Google Fonts accepts a subset of all possible weights when passing the argument, not an interval.

We stay as close as possible to the default @font-face spec: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face

A font-weight value. Accepts two values to specify a range that is supported by a font-face, for example font-weight: 100 400.

"As close as possible" in this case means that we're trying to keep things consistent, and this should be a string. If we want to add multiple values, then it's wiser (though it will take a few more lines to write) to define them separately. If we want to use a range, then 2 space-separated values make sense. The code I pushed for this tweak, _does_ allow for multiple values separated by a space... So if you use 400 700 it will be a range, but 100 400 900 will load those 3. However, that was a happy accident and not the spec. In order to avoid overcomplicating the code - and considering this side-effect doesn't have a negative impact, I decided to leave in to make life easier. Should we guard against that easter-egg?

I think it will introduce ambiguity. 400 700 is a range for the font-weight property, but two individual values for Google Fonts API. That's why I think we should make it explicit that we are requiring either a range or a subset.

If we really want to stick to a string, maybe formatting ranges as400-700 (we can replace the - for a when emitting CSS) would make it clearer that we're specifying an interval, not two individual values.

But then again, I get the feeling we're using the wrong tool for the job. We're dealing with an array/range/subset of numbers, but using the string data type? No hard feelings for me if we agree to go with the current solution, but I'd feel confused, as I did, if I was running the sample code on my WP installation.

This ticket was mentioned in Slack in #core by audrasjb. View the logs.


3 years ago

dingo-d commented on PR #1736:


3 years ago
#90

Can I just say how great this code looks from a developer's perspective? It uses design patterns, it's careful of separation of concerns, covered with tests, and is built future-forward (with PHP 8+ compatibility in mind).

I really enjoyed going through it and seeing this interesting implementation. It sets a great precedent on how PHP should be written in the future for the WordPress core IMO.

Great work @hellofromtonya @aristath and others who contributed 🚀

azaozz commented on PR #1736:


3 years ago
#91

The Validator exists first and foremost validate required keys exist and their value is of the right data type. Why? To avoid PHP 8.1 deprecation notices for wrong data types, fatal errors from missing required keys, and future with PHP 9 fatal errors from wrong data types.

That's great. The problem is not with what it does, the problem is with where the data that is being validated comes from. Think we talked about this couple of times already. Not sure why it needs to be repeated again and again... :)

I can see this validator useful at the upper layer functionality, not at the lower layer API. At the lower layer (in all currently explored use cases) it keeps validating hard-coded settings, which is quite pointless.

In that terms the best way forward would be to make the validator available to the upper level functions, to plugins and themes, as a separate "helper functionality". Then these upper level functions can use it when needed and not run hard-coded, already validated settings through it.

For example: if the webfonts settings are "dynamic" and not pre-set, and can be changed by the user which is a very unlikely use case as it doesn't have practical merit), the theme or plugin will be able to do something like:
{{{php
$generated_webfont_settings = array( ... );
wp_register_webfonts( wp_validate_webfonts_settings( $generated_webfont_settings ) );
}}}
where wp_validate_webfonts_settings() can provide custom errors and try to be more helpful in general. Another possible use of wp_validate_webfonts_settings() could be during the development of the code that's going to use the wenbonts API.

azaozz commented on PR #1736:


3 years ago
#92

What happens if a plugin or theme does not validate and it causes errors or unexpected styling for users?

Same as any other WordPress plugin or theme: it won't get released as it won't work. Have you tried doing that? :)

jorgefilipecosta commented on PR #1736:


3 years ago
#93

There are variable fonts like https://fonts.google.com/specimen/Open+Sans?standard-styles=&vfonly=true e.g: fonts that just with one asset can be used to render a range of font weigths like 323, 434. Should our api have a way to indicate we are loading a variable font?

jorgefilipecosta commented on PR #1736:


3 years ago
#94

Are the any plans to say a font could support like 200, 400, 600, 700 in regular and italic and in the UI for picking font family and weight we show all these options, but we don't actually enqueue all these options we would only enqueue the weights that are actually in used in a style inside theme.json or in a block level styles?
Basically I'm asking if there could be in the future a distinction between font weights and styles being available from being enqued e.g: we can say we support a big number of weights but not enqueue them, e.g: only enqueue if there is a global style or block style using them.

I guess we probably don't need this distinction as the cost of enqueue many weights is not big the browsers are probably smart and don't request unused weights.

jorgefilipecosta commented on PR #1736:


3 years ago
#95

I like the general direction of the work being done here. Thank you to all that contributed to this PR 👍
It will for sure open the door to make many google fonts available from theme.json in a simple way. I'm also eager to see the information this API contains e.g: weights and styles available in font family connected to the editor so the user can not choose a weight that is not actually available. Having this information will make things more intuitive to the users.

hellofromtonya commented on PR #1736:


3 years ago
#96

Are the any plans to say a font could support like 200, 400, 600, 700 in regular and italic and in the UI for picking font family and weight we show all these options, but we don't actually enqueue all these options we would only enqueue the weights that are actually in used in a style inside theme.json or in a block level styles?

Basically I'm asking if there could be in the future a distinction between font weights and styles being available from being enqued e.g: we can say we support a big number of weights but not enqueue them, e.g: only enqueue if there is a global style or block style using them.

@jorgefilipecosta I can envision this is a future enhancement. It would take some discussion and experiment to figure out how to make the API aware of what webfonts and variations are selected globally and for the specific webpage.

hellofromtonya commented on PR #1736:


3 years ago
#97

@azaozz both blockers you identified are now _unblocked_:

Handling/resolving the site visitor and user privacy issues when loading fonts from external APIs is still pending. Think there's an understanding what can be done here for it.

Per our discussion and agreement, by default, the API does not make remote requests to font provider services.

A filter (called 'has_remote_webfonts_request_permission') is provided here to grant the API permission (see https://github.com/WordPress/wordpress-develop/pull/1736#discussion_r743930877).

If permission is not given, the API will not create @font-face styles for remote font provider services including for Google Fonts.

The documentation has been improved but thinking quite a bit more is needed. Left some inline comments but they would probably be better here.

Extensive in-code documentation/comments is added to class docblocks, global functions docblocks, and public methods docblocks. And there's a full suite of tests to show developers what the expected behavior is for different kinds of data sets.

#98 @hellofromTonya
3 years ago

  • Keywords needs-dev-note added

aristath commented on PR #1736:


3 years ago
#99

There are variable fonts like https://fonts.google.com/specimen/Open+Sans?standard-styles=&vfonly=true e.g: fonts that just with one asset can be used to render a range of font weigths like 323, 434. Should our api have a way to indicate we are loading a variable font?

The API supports all CSS values that a variable font can use. Using font-weight: 100 900 will define a _range_ of font-weights - including all values that a variable font can have 👍

aristath commented on PR #1736:


3 years ago
#100

Basically I'm asking if there could be in the future a distinction between font weights and styles being available from being enqued e.g: we can say we support a big number of weights but not enqueue them, e.g: only enqueue if there is a global style or block style using them.

It should be possible to do that in the future, but that will require an implementation on the Gutenberg front too so can't be implemented in this 1st implementation.

I guess we probably don't need this distinction as the cost of enqueue many weights is not big the browsers are probably smart and don't request unused weights.

Yeah, it would be a micro-optimization and not something with any noticeable impact. Like you said, browsers are smart and only load the font-weights and unicode ranges they need to render the content 👍

aristath commented on PR #1736:


3 years ago
#101

I'm also eager to see the information this API contains e.g: weights and styles available in font family connected to the editor so the user can not choose a weight that is not actually available. Having this information will make things more intuitive to the users

Agreed, with this implementation it should be easy to find out which font-weights are registered and only display those in the editor 👍

hellofromtonya commented on PR #1736:


3 years ago
#102

As this PR has been thoroughly reviewed by multiple folks including several core committers, pending no further feedback / code review issues, targeting this PR for commit tomorrow before the 5.9 feature freeze deadline.

hellofromtonya commented on PR #1736:


3 years ago
#103

As this PR has been thoroughly reviewed by multiple folks including several core committers, pending no further feedback / code review issues, targeting this PR for commit tomorrow before the 5.9 feature freeze deadline.

#104 @hellofromTonya
3 years ago

  • Keywords commit added

Marking PR 1736 for commit but will wait until tomorrow to commit it. Why? To give folks more time to do code reviews and provide feedback.

Last edited 3 years ago by hellofromTonya (previous) (diff)

azaozz commented on PR #1736:


3 years ago
#105

Been thinking about this patch, and the functionality it adds a lot during the last few weeks. Unfortunately many of the questions I had are still unanswered. Frankly I don't think it will be a good addition to WordPress in its current form at this moment.

Purely as code it looks good. It is really well documented (thanks @hellofromtonya!). However I still cannot see how this would make WordPress better in the short and long run. We were chatting with @draganescu and he suggested that ideally this should have been a feature plugin. Then it would have been possible really to test it in production, verify (or reject) the assumptions that were made while creating it, and make it into a really worthy addition to WordPress.

The unanswered questions are mostly about the functionality this patch would bring to WordPress, i.e. about why things are being done this way and not in a simpler, more straightforward ways:

  • Why does WordPress need to introduce a new, special, "proprietary" syntax that extenders must use to enqueue web fonts? This doesn't seem to make sense at this point. There are well established ways to define web fonts for use in a web page, making up another syntax for it doesn't seem like a good idea.
  • What is the need for having "providers" that are "registered" separately from the web fonts instead of as part of the actual fonts. What (current) problems does that solve?
  • What is the point of having a "schema" and a schema verification in a lower-level, private API? What use cases does that fix? Why not add (or make it possible to add) that verification (and not call it "schema") in the upper-level functionality where it belongs?
  • What happens when that schema needs to be updated? Will there be v.1, v.2, etc. of it? How is that doing to be helpful to plugins and themes?

There are lots of answers "in principle" to these and similar questions, but few of them looks at the practical side of why all this is done. Most seem based on assumptions. (BTW this triggers one of the worse bugs in GitHub UI: parts of the discussion is "folded". That makes it really hard to follow. Please make sure to unfold it).

One answer is that all of this is needed in order to optimize web fonts loading by combining the requests when several plugins add similar fonts. However when using HTTP/2 this optimization is not only not needed but can make things slower as it makes browser caching more difficult. Currently just under 50% of the web servers use HTTP/2, however literally all big/popular web sites use it, so by "page loads" over 80% of the internet runs on HTTP/2. That makes this type of optimization in WP "questionable" at best. Would probably be better to not do it.

I see all the work and efforts that went into making this patch and really want to acknowledge it. However thinking that the "best next step" is perhaps to test all the assumptions that went into creating this patch in practice. Ideally there can also be an alternate approach that does only what is needed to enqueue a web font without all the complexities of the current patch.

jdevalk commented on PR #1736:


3 years ago
#106

I disagree with @azaozz and think we should just merge this.

aristath commented on PR #1736:


3 years ago
#107

Purely as code it looks good. It is really well documented

Good to know, thank you for approving the code! 👍

I still cannot see how this would make WordPress better in the short and long run

The webfonts API is a prerequisite for many future improvements we want to make in Gutenberg and block themes:

  • Allow defining webfonts in a theme.json file
  • Allow webfont previews in the font-family selector in the editor
  • Allow showing valid font-weights for a font-family in the editor instead of all the invalid weights we currently show
  • Improve frontend performance.
  • Allow localizing (server-side hosting/serving) webfonts to improve performance & privacy

Those are just the ones that directly involve Gutenberg and the first ones that came to mind. There are a dozen practical reasons that will benefit themes and the ecosystem as a whole, some of them have already been mentioned in previous comments but I can post them again if necessary.
There are many improvements in Gutenberg that are in limbo, waiting for a webfonts API. Not having a webfonts API is a blocker at this point. It's not a good-to-have item in our wishlist, it is a requirement in order to move forward.

Sure, there are workarounds for everything and we've done fine for years without a webfonts API. But everything we've been doing is just that: workarounds because we lack an API. Let's not perpetuate a bad situation when we can do better and improve things... In theory we don't need a styles or scripts API either, plugins and themes could inject their scripts and styles in wp_head and things would work. But we have these APIs because they bring consistency to chaos. They allowed us to improve WordPress as a whole, and webfonts are no different.

Why does WordPress need to introduce a new, special, "proprietary" syntax that extenders must use

There's nothing proprietary about it. It's a simple array using @font-face CSS-props as keys, and CSS values as values.

What is the need for having "providers" that are "registered" separately from the web fonts instead of as part of the actual fonts. What (current) problems does that solve?

It allows plugin extenders to implement other, proprietary webfonts-providers - such as Adobe fonts - and also leaves the door open for core to add more providers in the future if and when another open-source webfonts provider exists.

What is the point of having a "schema" and a schema verification in a lower-level, private API? What use cases does that fix? Why not add (or make it possible to add) that verification to the upper-level functionality where it belongs?

The fact that some developers (myself included) would have done things differently does not mean that this implementation is any less valid or good.

What happens when that schema needs to be updated?

The same thing that happens when any schema in WordPress gets updated. Using a schema in WordPress is not a revolutionary concept. There is precedent and these schemas get updated over time. This API was built with the future in mind, so I really don't expect updates to the schema any time soon. If and when the time comes to update the schema, it's pretty easy to extend, tweak, and the structure used allows for future implementations. We can't block this patch because in theory, at some point in the future, we might need to change the schema... It's been done before. Changing schemas is doable and has been done (most recently for the theme.json schema). Nothing bad happened.

There are lots of answers "in principle" to these and similar questions, but few of them looks at the practical side of why all this is done.

I disagree. All our answers are very practical, and to the point, with very specific examples of why we need this. The only thing I see being "in principle", is the objections above.

One answer is that all of this is needed in order to optimize web fonts loading by combining the requests when several plugins add similar fonts. However when using HTTP/2 this optimization is not only not needed but can make things slower as it makes browser caching more difficult. Currently just under 50% of the web servers use HTTP/2, however literally all big/popular web sites use it, so by "page loads" over 80% of the internet runs on HTTP/2. That makes this type of optimization in WP "questionable" at best. Would probably be better to not do it.

If I'm not mistaken, when "combining requests" was mentioned, it was referring to server-side requests, nothing to do with client-side requests, HTTP/2 etc.

As for feature plugins... Feature plugins in theory are a good idea. In practice however, they never worked the way the community hoped. There are examples of some feature plugins doing good (REST API for example), but these are the exceptions and not the norm. People don't use or test feature plugins (except for those who test them when they are considered for merging). Suggesting this be done as a feature plugin is an elegant way to delay something for a few years.

jffng commented on PR #1736:


3 years ago
#108

From a theme developer's perspective, this is a very welcome addition.

Here are few practical reasons why the API is useful. Many of these reasons have already been cited, but it doesn't hurt to reiterate support:

  • Makes supplying a custom font in WordPress reliable. I do not know the number of hours I've lost trying to figure out why my font is not working in the editor but it is in the front-end. I wish I could get those hours back, and that frustration contributed to some unpleasant feelings around working with WordPress when I first started.
  • Provides a standard and familiar (the API mirrors CSS font-face rule) way to supply a custom font. Without this feature, theme developers are left to handle this complexity on their own.
  • Reduces the amount of code needed for theme and plugin developers to do so.
  • Handles both local and google fonts, and creates a pathway to extend to more more providers.

Thanks for all your work on this PR, I hope to see it land soon.

zaguiini commented on PR #1736:


3 years ago
#109

Hi!

This was left unanswered and I'd like to get an explanation! Thanks!

felixarntz commented on PR #1736:


3 years ago
#110

I believe the reason on why this API is useful has been explained several times in the comments on this PR - we keep rehashing the same discussion over and over. From my perspective:

  • The API is not complex. It is just as simple to use as the existing APIs to register CSS and JS assets.
  • The API allows optimizing how web fonts are loaded for performance. Manually doing that requires every theme developer to know all these techniques, and even then it doesn't solve it as soon as multiple sources (e.g. also a plugin) want to use web fonts at the same time.
  • A centralized API layer here is not "proprietary" - it has a purpose (see above and the various comments on this PR). Let's look at wp_enqueue_style() and wp_enqueue_script() - why do they exist? A developer could with fewer lines of code just write <link rel="stylesheet"... - are these APIs therefore useless? They aren't because they allow to manage these assets in a centralized layer, which allows for things like dependency management and performance optimization.
  • A feature plugin for a new API like this doesn't make sense. APIs in plugins don't typically get widely adopted (unless it's a plugin with millions of installs), so we need to ensure through review and rounds of feedback that it's consistent and sound.

I think there is great value in shipping this API in core which has been outlined several times, and I don't agree on the pushback for why it shouldn't be merged. That said, it is important that the merge goes hand in hand with:

  • Promoting the API in a post on Make Core (and maybe also Make Themes).
  • Provide examples on how to use it in themes, including how to migrate existing code using web fonts.
  • Through those (and maybe other) channels gather more feedback by theme developers prior to release.

#111 @hellofromTonya
3 years ago

  • Keywords commit removed
  • Milestone changed from 5.9 to Future Release

Hello everyone,

Thank you for contributions and support of the Webfonts API. A lot of work went into getting to its current state.

The collaboration and review process has been significant, especially in such an accelerated timeframe. It has good support including at least 4 Core Committers. But there are open questions and a Lead Dev has raised the red flag.

Whether we agree or disagree, this is an opportunity to think about the best way forward for this API and web fonts in Core.

As @azaozz noted, Core currently does not use this API. But what if it did?

@aristath shared a list of features and improvement initiatives for Gutenberg and block themes. That's the bigger picture of the next steps for web fonts. A big driver to get into 5.9 was to support these initiatives.

Move it Gutenberg First Proposal

What if it were in Gutenberg first?

Then the list could be built onto of it. Think about it.

Being in Gutenberg as part of building the list can mean:

  • Gets used in the editor and by theme.json experimentations
  • Changes don't need to support backwards-compatibility (until it merges into Core)
  • Changes can ebb and flow faster to support the features
  • Gains experimentation, learning, feedback, live testing, and lots of eyes on it
  • Proves itself by being a foundational piece of these features and improvements
  • Matures in the Gutenberg workflow process to become more robust, reliable, and stable

When each thing on the list is merged into Core, so is the API. Welcome to Core.

The Next Step Forward

Today is 5.9 Feature Freeze. The API brings a lot of new code. It can benefit from more feedback (especially from theme developers) and actual usage. It needs real world examples.

Given the concerns raised combined with the advantages of being part of the new features and improvements in Gutenberg, I'm punting it to a Future Release and advocating to move it to Gutenberg. Time ran out in this cycle.

Core needs this API for all the reasons laid out in the PR and the proposal. Let's take the time to vet it and ensure it's ready to deliver on its potential.

Punting today is not an end; it's another step forward towards web fonts coming to Core.

creativecoder commented on PR #1736:


3 years ago
#112

Given that a lot of the motivation for this API seems to be supporting features related to the block editor and block-based themes using theme.json, why not introduce the API in the Gutenberg plugin first? There it can be real world tested and iterated on as needed, any underlying assumptions validated, and then merged to Core in a future release.

That seems like the standard path we now have for new editor and block theme related features... it's not clear to me why this one is proposed to go directly into Core WP.

hellofromtonya commented on PR #1736:


3 years ago
#113

@creativecoder Yes, that's the clearest path for this API. See the comment here in the Trac ticket. The API will not ship with 5.9 (reasonings are laid out in that comment). I'd advocate that this API move to Gutenberg to support the features and improvements @aristath identified. Then when those features/improvements are ready and merged into Core, so is the API.

azaozz commented on PR #1736:


3 years ago
#114

@SergeyBiryukov

I think I see the point here, one way to implement this API could be to keep it basic and follow the existing scripts and styles functions. That could work well as a minimum viable product.

Yes, that was the initial idea as far as I see. That's how most new functionality usually gets added. However during implementation it was made into a pretty big, complex API that introduces quite a few new requirements. It even needs its own sub-directory now :)

There is nothing wrong with either approach.

Yep, I agree. Adding a bigger new API to WordPress usually entails quite a bit of "real-life" testing. That usually works by making it into a feature plugin. Unfortunately there wasn't enough time to do that and properly test this patch for 5.9, hence the delay.

I think the explanations given address the "what is the need" concern quite well: providers, schemas, registries, etc. are not a new concept in core, and it makes sense to use them for newer APIs as appropriate.

Yes, you're right. However I see couple of places in how things work in this patch that may need some more thinking/justification. One is with a lower-level, private API having a "settings verification" (a.k.a. schema in the patch). In addition it doesn't seem to care what code is using the API, i.e. where are the webfont settings coming from, i.e. it ignores the use cases. Don't think there are any other examples of such approach in WP.

The other is the need of having "providers" as a separate setting. That seems to prevent using of the webfont settings/resources as supplied by some (all?) third-party APIs. For example after selecting a webfont from Google it gives you an URL or some CSS that you're supposed to use. However that URL or the CSS cannot be used with this patch. They have to be "converted". That's not a huge deal, but... Why the inconvenience and the requirement for extra work? Also, why the possibility of edge cases introduced by that conversion?

Another way is the current PR, which not just offers the basic functionality but takes it to the next level by modernizing it, making it more testable and future-proof.

Right, IMHO it very much depends on what is meant by "modernizing it". If looking at the bigger picture, this patch, as currently written, is very restrictive towards plugins and themes. It tries to "micro manage" all aspects of adding a webfont to WordPress by preventing the direct use of CSS (which is the standard/traditional way of doing this).

Looking at the even bigger picture, this attempt for micro-managing/locking-down all aspects of the functionality prevents most extensibility. It tries to treat themes and plugins as a restricted, "second class" citizens which is a big departure from the WordPress code architecture. For better or worse in WordPress themes and plugins are considered "first class" citizens and are allowed and encouraged to plug-in everywhere and change a lot of things in order to achieve their goals. This is one of the most important features in WP, and probably the main reason why over 40% of the top 10 million sites use it.

The "code architecture" pattern used in this patch is quite good. It is probably used in lots and lots of other software, perhaps also in many of the other CMS apps out there. However it doesn't fit in the general design patterns used in WordPress as "demoting" themes and plugins to second class is not a good idea. Having said that, as the main functionality of this patch is really simple, that architectural difference probably won't cause many problems, but still better to keep it consistent with the rest of WordPress.

On the other hand if "modernizing it" means now the code is written in a "pure" OOP way, it seems fine. This writing style may be a bit different than any other code currently in WordPress, but I don't think this is a problem.

In my opinion the best way forward would be to make this patch into a feature plugin as it should have been from the start considering its size and complexity. This would allow for much better testing, and rapid fixing of all discovered bugs and problems.

azaozz commented on PR #1736:


3 years ago
#115

@felixarntz

I believe the reason on why this API is useful...

Yes, I believe this API will be quite useful to themes and perhaps to some plugins.

The API is not complex. It is just as simple to use as the existing APIs to register CSS and JS assets.

Not so sure about that. Have you tried to make an Adobe webfont work with it? What about other suppliers of webfonts, there seem to be several out there.

A feature plugin for a new API like this doesn't make sense.

Sorry but I disagree. Feature plugins have been used in WordPress for quite a few years now. Pretty much all larger new features were added from a feature plugin. The main benefit of having a feature plugin is that the new code can be tested easily by more people at more places, and that any bugs can be fixed much faster, and new versions of the plugin released to continue the testing. If such code was committed to WordPress any bugs would have to wait for at least a month before getting fixed (or if the bugs were severe enough like causing fatal errors, minor versions of WP would need to be released and auto-updates pushed).

felixarntz commented on PR #1736:


3 years ago
#116

@azaozz Wanted to follow up on this point:

Sorry but I disagree. Feature plugins have been used in WordPress for quite a few years now. Pretty much all larger new features were added from a feature plugin. The main benefit of having a feature plugin is that the new code can be tested easily by more people at more places, and that any bugs can be fixed much faster, and new versions of the plugin released to continue the testing. If such code was committed to WordPress any bugs would have to wait for at least a month before getting fixed (or if the bugs were severe enough like causing fatal errors, minor versions of WP would need to be released and auto-updates pushed).

You're pointing out why feature plugins are generally useful, all of which I 100% agree with. What I'm questioning though is the benefits of a feature plugin for an API that doesn't do anything in WordPress itself, but is only useful for other developers. People installing a plugin with this API in their sites will give them nothing to test, it entirely depends on adoption by other developers, which is why I question the feature plugin approach here. I may be missing something, but can you point me to any (previous) feature plugin that is purely an API and no functionality out-of-the-box?

aristath commented on PR #1736:


3 years ago
#117

Have you tried to make an Adobe webfont work with it? What about other suppliers of webfonts, there seem to be several out there.

I haven't tried adding Adobe fonts because they are proprietary, require an API key and therefore doesn't fit in core. I'm sure it can be done in a plugin though, with its own options for API keys etc, extending the core API.
That being said, I initially built a fontlibrary.org class while experimenting, to be sure that we can add other providers in the future. Needless to say, it was ridiculously easy to do. However, that provider is not as well maintained as google-fonts, the resulting styles from that API are less than good (to put it nicely), and the licensing is more complicated - which is why no other providers were added.
Are you aware of any providers that use open-source fonts that we should include? During my research I didn't find any, but I'd be more than happy to implement them if and when one becomes available.

For reference and testing purposes, here's the code for the fontlibrary.org provider:
{{{php
class WP_Webfonts_Font_Library_Org_Provider extends WP_Webfonts_Provider {

protected $id = 'fontlibrary';
protected $root_url = 'https://fontlibrary.org/';


public function get_css() {

$css = ;
foreach ( $this->webfonts as $webfont ) {

$url = $this->root_url . '/face/' . sanitize_title( $webfontfont-family? );
$css .= $this->get_cached_remote_styles( 'fontlibrary_' . md5( $url ), $url );

}
$css = str_replace( "('/assets/", "('{$this->root_url}assets/", $css );

return $css;

}

}
wp_register_webfont_provider( 'WP_Webfonts_Font_Library_Org_Provider' );
}}}

Yep, that's all it takes to add a new provider 😄

azaozz commented on PR #1736:


3 years ago
#118

@felixarntz Sorry for the delay in responding. Have some health problems.

You're pointing out why feature plugins are generally useful...

Right. I believe this API, like all other larger/more complex new features in WordPress, would benefit from being a feature plugin and get tested in all sorts of environments, staging, etc. That includes testing at very large sites, multi-site/network installs, etc.

People installing a plugin with this API in their sites will give them nothing to test..

Correct, that's a similar case like with the REST API. Feature plugins can be targeted at extenders too, not just at site admins.

The alternative would be to add a brand new, untested, potentially unrefined API which would "commit" WP to support it "forever" even if there are large problems with it (I don't think there are, but it is a possibility). And it is not an MVP, it adds a lot of more advanced features. Hence my recommendation to be on the cautious side, and I believe the patch was not merged following that recommendation. Also identified two "blockers" (documentation, user privacy) that were fixed in time.

azaozz commented on PR #1736:


3 years ago
#119

@aristath Again, sorry about the delay, have some health problems at the moment.

I haven't tried adding Adobe fonts...

Specifically asked about Adobe as they have probably the largest fonts library and guessing they are the top candidate for being added by plugins that want to extend the WP webfonts API. Also their APIs work in a pretty different way. As far as I see they expect the users to have an account on their website and to manage the webfonts features from there. Here are some examples in this tutorial: https://helpx.adobe.com/fonts/using/add-fonts-website.html. As far as I see it works by providing an URL that doesn't contain any fonts information, just points to the user's account on their site. So all the fonts settings that are required by this patch are not available.

As far as I see there is no way to make this work with the current patch. Perhaps if the patch was more extensible a plugin may be able to bypass most of the current requirements and enable use of Adobe fonts, but still looks like a pretty hard thing to do.

...to be sure that we can add other providers in the future

I'm actually still wondering about having the "providers" registration in the first place. Could you perhaps tell me how did you come up with that idea, to separate the font URL into two parts and what benefits that brings?

At the moment, no matter from what angle I try to look at it, it seems that the "providers" abstraction is not needed/not performing a particular function. Having the fonts registered together with the providers (i.e. the providers are part of the font registration) seems more feasible. That would also reduce the current complexity quite a lot, and make the API easier to use.

Another idea/concern/enhancement would be to separate the font settings that are for use by other parts of WP (like the editor) and the actual URL used to get the font(s). For example looking at how Google serves webfonts, having to "extract" the settings from the URL that is generated by the Google API, then enter them as PHP array key/value pairs, then the API would try to regenerate the original URL as given by the remote API from the settings is not the best way for this to work imho.

Perhaps a better solution would be to distinguish between "functional" webfonts settings (as needed for the webfont to work) and settings intended for other parts of WP. Then a theme or plugin will be able to expose some of the settings to the editor UI, but keep others "hidden" from the users: a lot better control and a lot simpler/safer as the original third-party API URL will not be disassembled by the plugin author and then reassembled by WP.

Please note: this is just an idea, not a "blocker" or anything like that :)

aristath commented on PR #1736:


3 years ago
#120

sorry about the delay, have some health problems at the moment.

I'm sorry to hear than Andrew, I hope things go well and you feel better soon ❤️

Specifically asked about Adobe as they have probably the largest fonts library and guessing they are the top candidate for being added by plugins that want to extend the WP webfonts API. Also their APIs work in a pretty different way.

I'm sorry but I'm not going to try and fix the way the Adobe API works. If Adobe wants, they can build a class themselves - or fix their API to be more friendly/reasonable.

I'm actually still wondering about having the "providers" registration in the first place. Could you perhaps tell me how did you come up with that idea, to separate the font URL into two parts and what benefits that brings?

The benefit is consistency. All webfonts can be defined the same way - regardless of whether they are local, google-fonts, or something else. This is necessary so that webfonts can be defined in a theme.json file using a consistent format regardless of their source of origin. That can't be done efficiently without some concept of a provider.

For example looking at how Google serves webfonts, having to "extract" the settings from the URL that is generated by the Google API, then enter them as PHP array key/value pairs, then the API would try to regenerate the original URL as given by the remote API from the settings is not the best way for this to work imho.

That's a false premise... You're missing a step here, and that step is the very 1st one: The user chooses a webfont they want to use. Users don't "extract the settings from the generated URL to then enter them as PHP array key/value pairs"... Users don't have a URL in their mind when they want to use the Open-Sans font with weights 400 & 700. They start with what they want to use which is a font-family, some font-weights etc. Instead of going to the google-fonts (or any other API) to generate a URL, they just enter what they want as key/value pairs - either in PHP or JSON in their theme.json file.

Currently:

  • The user decides what webfonts they want to use
  • They go to the google-fonts site, select their fonts
  • The google-fonts API generates a URL which the user then has to use.
  • The user then needs to add that to their site. If they add it using add_action( 'wp_head', ... ) they can just inject it directly (hacky solution). If they use wp_enqueue_style (the currently "right" way), then it doesn't work when they have multiple fonts in the same google-fonts request.

With the Webfonts API:

  • The user decides what webfonts they want to use
  • They enter the font details in an array format
  • The API takes care of the rest.

I don't see how the current, unmanaged version is any better than a more managed solution which doesn't require users to go to a 3rd-party site, search for webfonts, then get a URL and manually handle adding it everywhere.
If someone wants to do things the "old" way there's nothing preventing them to do that. But in that case they need to manually take care of adding the webfont in the frontend and the editor, add it to font-family pickers, manage privacy concerns, optimize delivery and so on.
This API does not break current behaviors. It doesn't prevent anyone from doing something they were previously doing. If someone wants to manually inject webfonts, styles, scripts and whatnot on their site they'll always be able to do that. Having an API makes the process easier for those that need it.

Perhaps a better solution would be to distinguish between "functional" webfonts settings (as needed for the webfont to work) and settings intended for other parts of WP.

What would be a non-functional webfont-setting exactly? The API simply uses @font-face props - which by definition makes them all functional. The only "arbitrary" setting is defining the provider which simply tells the API "hey, this webfont should be fetched from a 3rd-party server". Is that the non-functional part?

azaozz commented on PR #1736:


3 years ago
#121

@aristath

I hope things go well and you feel better soon

Thank you!

I'm not going to try and fix the way the Adobe API works

Hehe, of course not. But not being able to use the Adobe fonts API/CDN with the WordPress webfonts API seems like big drawback. Themes and plugins should be able to do that.

On the other hand looking at the other font foundries that provide webfonts, most would let you download the font and host it locally (whether is it paid or free to use). There is also https://fontsource.org/ and its repo: https://github.com/fontsource/fontsource that seem to contain the fonts that are available from Google.

Then the consideration becomes: does WordPress want to facilitate usage of third party webfonts APIs/CDNs at this stage? Seems quite better to make the WP API work only with local fonts for now. This makes it quite simpler, and also resolves any concerns about user privacy issues. Also removes the necessity of integration with a future "user consent API" that may be added later. In that case not being able to extend the API to use Adobe webfonts CDNs for now seems appropriate. Frankly I'm starting to think this is the proper way forward.

The benefit is consistency. All webfonts can be defined the same way - regardless of whether they are local, google-fonts, or something else.

Hmm, yes, I can see that but generally a webfont is defined by its URL. Third-party/remote webfonts would have absolute URLs, local fonts would probably need to have relative URLs. This is the only difference, and is really "easy to handle". Don't think the abstraction is needed there.

Furthermore this abstraction seems to only be used when using the Google CDN. Considering the above thoughts (only local fonts for now) think the need for abstracting the fonts "sources" disappears completely.

You're missing a step here, and that step is the very 1st one

Okay, this only concerns using the Google webfonts API/CDN so probably not that important for now. When you select a webfont there (with few options) you're given an URL like this: https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,900;1,100&display=swap. This URL cannot be used in the WP webfonts API as given by the remote service. It has to be "translated", right? This is the step that seems "strange", WP not being able to accept what the third party APIs suggest and expect.

I understand that the different options that were selected have to be "known" so they can be used by the editor UI for example. My suggestion in this particular case was to separate the "working URL as given by Google" from the options that are presented to the WP UI.

This is something to maybe examine more closely in the future if WordPress decides to add support for using the Google CDN. At that time a decision would have to be made about using the Adobe CDN too.

azaozz commented on PR #1736:


3 years ago
#122

Re-posting this here from https://github.com/WordPress/gutenberg/pull/36394 (having 2 PRs for the same thing is hard/confusing...).

Been looking at this implementation for a while. It seems that it can be made a lot better by removing the "providers" abstraction. Few reasons:

  • It will remove several possible (theoretical) edge cases that are a concern: registering the same font from multiple providers, different plugins registering the same "provider" (conflicting settings?), etc.
  • Seems that the "providers" abstraction can only be used to add support for the Google webfonts API/CDN. Seems the Adobe API/CDN cannot be made to work with this implementation as it expects the user to manage all features from the Adobe website (doesn't pass font settings in the URL like for the Google's CDN).

Advantages of using local webfonts (from Fontsource):

  • Self-hosting brings significant performance gains as loading fonts from hosted services, such as Google Fonts, lead to an extra (render blocking) network request. To provide perspective, for simple websites it has been seen to double visual load times. Benchmarks can be found here and here.
  • Fonts remain version locked. Google often pushes updates to their fonts without notice, which may interfere with your live production projects. Manage your fonts like any other NPM dependency.
  • Commit to privacy. Google does track the usage of their fonts and for those who are extremely privacy concerned, self-hosting is an alternative.
  • Your fonts load offline. On top of benefiting PWAs, often there may be situations, like working in an airplane or train, leaving you stranded without access to your online hosted fonts. Have the ability to keep working under any circumstance.
  • Support for fonts outside the Google Font ecosystem. This repository is constantly evolving with other Open Source fonts. Feel free to contribute!

In these terms my recommendations are:

  1. Add support only for local fonts for now. If WordPress decides to include support for the Google CDN later on, the implementation will have to consider web privacy laws and restrictions and be tied with an eventual User Consent API, etc.
  2. Because of the above the "providers" abstraction can be removed as it is not needed.
  3. The "WP webfont settings validation" functionality (a.k.a. "schema" in the patch) should be moved out of the low-level API and into a separate function. The API should only check parameter types (to prevent fatal errors as it seems this may be broken in new versions of PHP). However regex is not needed to check/set function param types, that is redundant there.

hellofromtonya commented on PR #1736:


3 years ago
#123

The Webfonts API has moved to Gutenberg (see first implementation in https://github.com/WordPress/gutenberg/pull/36394). Closing this PR, thought it will remain available for the ongoing work in Gutenberg.

hellofromtonya commented on PR #1736:


3 years ago
#124

The Webfonts API has moved to Gutenberg (see first implementation in https://github.com/WordPress/gutenberg/pull/36394). Closing this PR, thought it will remain available for the ongoing work in Gutenberg.

azaozz commented on PR #1736:


3 years ago
#125

I'm still not convinced the Webfonts API should be "merged" with Gutenberg. It is a separate API after all. Another reason is that it still may benefit from being made into a feature plugin (no point to bypass the WP development process).

#126 @hellofromTonya
3 years ago

In 53282:

Themes: Add internal-only theme.json's webfonts handler (stopgap).

Adds _wp_theme_json_webfonts_handler() for handling fontFace declarations in a theme's theme.json file to generate the @font-face styles for both the editor and front-end.

Design notes:

  • It is not a public API, but rather an internal, Core-only handler.
  • It is a stopgap implementation that will be replaced when the public Webfonts API is introduced in Core.
  • The code design is intentional, albeit funky, with the purpose of avoiding backwards-compatibility issues when the public Webfonts API is introduced in Core.
    • It hides the inter-workings.
    • Does not exposing API ins and outs for external consumption.
    • Only works for theme.json.
    • Does not provide registration or enqueuing access for plugins.

For more context on the decision to include this stopgap and the Webfonts API, see:

Props aristath, hellofromTonya, peterwilsoncc, costdev, jffng, zieladam, gziolo, bph, jonoaldersonwp, desrosj.

See #55567, #46370.

#127 @hellofromTonya
3 years ago

In 53288:

Themes: Remove 'gutenberg' as translation context in _wp_theme_json_webfonts_handler().

Follow-up to [53282].

Props kebbet.

See #55567, #46370.

#128 follow-up: @grapplerulrich
2 years ago

@aristath or @hellofromTonya Would you be able to provide a short update what the next steps are? The reason I ask is that I came across a few different sites mentioning a PHP API, but from what I can see that did not make it in.

#129 in reply to: ↑ 128 @hellofromTonya
2 years ago

  • Keywords has-patch has-unit-tests removed

Replying to grapplerulrich:

@aristath or @hellofromTonya Would you be able to provide a short update what the next steps are? The reason I ask is that I came across a few different sites mentioning a PHP API, but from what I can see that did not make it in.

Hello @grapplerulrich, a formal PHP API was not ready to merge into Core in 6.0. A stopgate though was included in 6.0 that handled web fonts and style variations defined in the theme's theme.json file (see changeset [53282]).

Webfonts API

Where? In the Gutenberg repo
Project board: https://github.com/WordPress/gutenberg/projects/66
Ongoing roadmap: https://github.com/WordPress/gutenberg/issues/41479

Status: WIP project / feature coordination and wrangling

Next steps:

  1. This week: I'll share Mátias' vision, stages, and data flow and API diagrams in the ongoing roadmap issue.
  2. Then tasks prioritization will happen (with architectural needs likely being the first priority > i.e. to settle on the architecture for remaining work to build on top of it).

I invite and welcome you to contribute.

#130 follow-up: @grapplerulrich
2 years ago

Thank you, @hellofromTonya, for the update! Meanwhile, I stumbled across the Roadmap issue on GitHub.

As I could not find any official documentation, I have created a PR to update the `theme.json` schema. I hope I got it right. Welcome any feedback :D

#131 in reply to: ↑ 130 @hellofromTonya
2 years ago

Replying to grapplerulrich:

As I could not find any official documentation, I have created a PR to update the `theme.json` schema. I hope I got it right. Welcome any feedback :D

Awesome! Thanks. I added the PR to the project board and added a documentation section in the ongoing roadmap with a link to the PR.

#132 @hellofromTonya
2 years ago

  • Keywords needs-dev-note removed
  • Milestone changed from Future Release to 6.2
  • Summary changed from A proposal for creating an API to register and enqueue web fonts to Fonts API: a proposal for creating an API to register and enqueue web fonts

Hello everyone,
A lot of development work has happened and continues to happen in Gutenberg for the now called Fonts API. The Ongoing Roadmap is found here https://github.com/WordPress/gutenberg/issues/41479.

A stretch goal is to introduce the API during WP 6.2. A lot of work has to happen first. But it is possible. So I'm adding this ticket to the milestone.

If it is ready for 6.2, then the work done will be:

  • Remove the stopgap code.
  • Backport the Fonts API from Gutenberg to Core.

Once backported, this ticket can be closed.

Follow-up work (see the Roadmap) will have separate Trac tickets to align to each backport. Development and discussions will centralized in Gutenberg, rather than distributed across Trac and Gutenberg.

Want to help? There are a lot of open issues that need testers and developers. https://github.com/WordPress/gutenberg/issues/41479

This ticket was mentioned in Slack in #core by mukeshpanchal27. View the logs.


23 months ago

#134 @hellofromTonya
23 months ago

  • Milestone changed from 6.2 to Future Release

Status update:

The Fonts API will not be ready for 6.2. I shared the reasoning here in the Ongoing Roadmap tracker. Coping it here for transparency:

Update:

The Fonts API is not ready for introduction into WordPress Core for WP 6.2. Why?

The API needs several Gutenberg release cycles to stabilize after being completely rewritten and renamed. The renaming and latest rewrites are targeted for GB 15.1 which releases 1 day after 6.2 Beta 1. That's not enough time to ensure the API is ready.

Focus will be on:

  • Getting the API stabilized.
  • Assessing and improving performance.
  • Adding automatic font enqueuing for all fonts in global styles.
  • Developers upgrading to use the new API (as the BC layer is temporary).

All of these seem doable to target introduction into WP 6.3 alpha.

This feature is close, really close to coming to Core. But it needs more time to stabilize before it comes.

I do think it's doable for 6.3. And I will be focusing on it once 6.2 betas into RC. You can help. Interested? Come join the effort in Gutenberg.

I'm moving it off the 6.2 milestone. But since 6.3 isn't available yet, I'll set it to Future Release. Once available, it will move onto the 6.3. milestone.

This ticket was mentioned in Slack in #core-editor by hellofromtonya. View the logs.


23 months ago

#136 @ironprogrammer
22 months ago

  • Milestone changed from Future Release to 6.3

As follow-up to comment:134, moving to 6.3 milestone.

#137 @oglekler
19 months ago

  • Keywords needs-testing-info added

Goal: introducing the API in WP 6.3, more information comment from hellofromtonya, Apr 26, 2023

This ticket was mentioned in Slack in #core by mukeshpanchal27. View the logs.


19 months ago

#139 @domainsupport
18 months ago

Hi,

I am attempting to test this brilliant addition by using wp_deregister_font_family() to remove fonts added by the theme's theme.json.

It looks like the themes' fonts are added here ...

add_action( 'init', 'gutenberg_register_fonts_from_theme_json', 21 );

So in theory this would work ...

<?php
add_action( 'init', 'deregister_fonts_from_theme_json', 22 );
function deregister_fonts_from_theme_json() {
wp_deregister_font_family('ibm-plex-mono');
}

... but the font is still showing in the "Dashboard - Appearance - Editor - Styles - Typography - Text - Typography" dropdown.

Please can someone advise where this function should be called if not on the init hook with priority 22?

Thank you,

Oliver

#140 follow-ups: @hellofromTonya
18 months ago

@domainsupport You've discovered a missing integration with Theme JSON data layer. Let me explain.

wp_deregister_font_family() currently deregisters the font-family with the Fonts API, meaning that font's @font-face CSS will not be generated or printed. All of the theme defined fonts (defined with a theme's theme.json file) are automatically processed by Theme JSON Resolver. Those fonts are part of the Theme JSON data layer, meaning they exist outside of the Fonts API.

There's currently no integration between the Fonts API and the Theme JSON / Theme JSON Resolver to remove theme defined fonts from its data layer.

Can you open an issue in Gutenberg please? Thank you!

#141 in reply to: ↑ 140 @domainsupport
18 months ago

Replying to hellofromTonya:

There's currently no integration between the Fonts API and the Theme JSON / Theme JSON Resolver to remove theme defined fonts from its data layer.

Oh right! Does this mean that when wp_register_fonts() and wp_enqueue_fonts() is used that, likewise, fonts added this way will not be visible in the "Dashboard - Appearance - Editor - Styles - Typography - Text - Typography" dropdown or does it work OK this way around?

Can you open an issue in Gutenberg please? Thank you!

Absolutely. Just need to think about it first as I may already have a working solution.

Thank you,

Oliver

#142 @hellofromTonya
18 months ago

Does this mean that when wp_register_fonts() and wp_enqueue_fonts() is used that, likewise, fonts added this way will not be visible in the "Dashboard - Appearance - Editor - Styles - Typography - Text - Typography" dropdown or does it work OK this way around?

@domainsupport no, the integration exists now. Fonts that are programmatically registered, such as from a plugin, will be included in the Site Editor's > Styles > Typography font pickers for user selection.

Enqueuing though should be limited to what users select as their global fonts. There's work underway to automatically enqueue what users select.

#143 in reply to: ↑ 140 @domainsupport
18 months ago

Replying to hellofromTonya:

Can you open an issue in Gutenberg please? Thank you!

That's done https://github.com/WordPress/gutenberg/issues/51369

#144 @hellofromTonya
18 months ago

Update:

Yesterday, I shared an update in the Fonts API ongoing roadmap.

tl;dr
Big changes are coming to the Fonts API with a new font management system that includes a new Font Library for users to manage their fonts.

The Font Library is part of #58579 and is currently planned for 6.3. If the 6.3 Release Squad approves converting #58579 into a blessed task, then this ticket also needs to be converted into a blessed task to support the Font Library and font management features and workflow.

Assessments are underway to determine:

  • Changes needed in the Fonts API.
  • If what's in Core now could support the Font Library.

Once the assessments are done, I'll share them in both the Ongoing Roadmap and here in this Trac ticket.

#145 follow-up: @flixos90
18 months ago

@hellofromTonya Thank you for the update. Given the new direction, what would you propose regarding this ticket? Does it become invalid? Or is there still a purpose for it and we should punt to Future Release?

#146 in reply to: ↑ 145 @hellofromTonya
18 months ago

Replying to flixos90:

Given the new direction, what would you propose regarding this ticket? Does it become invalid? Or is there still a purpose for it and we should punt to Future Release?

Yes, this Trac ticket is still valid.

#58579 - the "Library" feature - is targeted for 6.3.

The "Library" feature includes the Font Library for users to manage their fonts. The Fonts API (this Trac ticket) is the underlying handler for server-side @font-face generation and printing of those fonts.

I'm currently assessing if what's in Core now (i.e. the stopgap code) can temporarily support the Font Library needs or if the Fonts API is also needed in 6.3.


#147 @francina
18 months ago

  • Milestone changed from 6.3 to 6.4

Punted to 6.4 based on this Slack conversation from June 26th, 2023
https://wordpress.slack.com/archives/C051Z1SKBDZ/p1687785812987579

#148 @oglekler
16 months ago

  • Keywords gutenberg-merge added

As far as I see, work is going on on full speed here: https://github.com/WordPress/gutenberg/issues/41479

#149 @hellofromTonya
16 months ago

  • Milestone 6.4 deleted
  • Resolution set to reported-upstream
  • Status changed from assigned to closed

The proposal in this ticket is focused on developing an API for allowing the registration and enqueuing of web fonts. As I previously noted in comment:146, the direction of fonts has changed to point where an API is no longer needed.

What's coming is a fonts management system with the introduction of:

  • Font Library: a fonts manager for WordPress. See #59166.
  • Font Face: the server-side @font-face styles generator and printer. See #59165.

This proposal provided the catalyst for an R&D process that eventually evolved into what is coming. It's been a long road with many twists and turns as learnings were gained with what was known at each point along the way. All of those learnings led to what is coming - a fonts management system.

The original problem statement raised in this proposal will be solved with the new fonts management system.

Loading custom fonts in a sub-optimal manner can cause significant performance and privacy issues. Anecdotally, inefficient font loading is frequently one of the biggest performance bottlenecks in many WordPress themes/sites.

This is, in part, because there hasn’t been a ‘best practice’ approach; there’s no ‘standard’ way of adding a font to a theme and ensuring that it’s loaded in a performance- and privacy-friendly manner.

But it will be solved in a different way than proposed in this ticket. Thus, I think this ticket can be closed, i.e. in favor of Trac tickets previously noted above.

I also think commits for Font Face (see #59165) should reference this ticket and contributors to this ticket should be given contribution props.

Learnings

Fast forward to today. I now realize that introducing the (Web)Fonts API into Core would have been problematic. How so?

The API caused data problems.

The API was the entry point for plugins (and classic themes) to present their fonts to users for their consideration, i.e. for users to decide if they wanted to use any of the plugin fonts globally on their website. It did this by injecting registered fonts into a higher level data layer that was used in the user interface.

Then at enqueue time, the API had to reconcile which fonts should be used. It was an active but separate participate in figuring out which fonts were to be used globally. While higher data levels are responsible for the decision making, this API also needed to be involved to know which fonts to enqueue and thus print. This caused problems, as I noted in #59165:

But problems happened with a low level service trying to inject fonts into higher data levels. A low level service should not be involved in the process of determining what fonts are being used and should be generated and printed. Rather, a low level service should be told "Hey, process these fonts."

Was not compatible with the Font Library.

Once the first prototype of the Font Library was presented, it was determined the Fonts API was not compatible with it and caused more problems such as:

  • plugins would need to register with the Fonts API and the Font Library.
  • plugins/themes could bypass the fonts manager via directly enqueuing their fonts, without users knowing.
  • font reconcilation process would become even more difficult and problematic, likely less stable and prone to inconsistencies.

It would have made debugging harder and maintaining more costly.

Get it out of the decision reconcilation process.

Previously the Fonts API's roles were:

  • Role 1: Generate and print the @font-face styles for all “enqueued” fonts.
  • Role 2: Provide a means for plugins to present fonts to users, who can then decide whether to use these fonts (i.e. through the Site Editor).

With the new fonts management direction, Role 2 was removed as it was covered within the Font Library:

Plugins will no longer interact with the Fonts API. Instead, they will integrate directly into the Font Library (once that capability exists).

Shifting the role removed the problems above, including removing it from the decision reconcilation process. In doing so, register and enqueue functionality was no longer needed. Thus, an API was not needed.

The vision of font management simplified what was originally the Fonts API. Parts of it were reimagined and reassembled, moving what was "register" closer to where users interact with those fonts.

Summary: this proposal led to a fonts manager.

All of these learnings along the way, each implementation, led to what is coming, which IMO is a better user experience and workflow than originally imagined. It was a R&D process.

Though it's been a long road filled with challenges and opportunities, it was the catalyst that eventually evolved into solving the problems raised in this proposal.

Thank you to each contributor! I invite you to stay involved in Font Face and Font Library.

#150 @hellofromTonya
16 months ago

In 56500:

Introduce font-face styles generator and printer.

Introducing Font Face, a server-side @font-face styles generator and printer.

tl;dr:

  • Introduces Font Face.
  • Deprecates _wp_theme_json_webfonts_handler().

Introduce Font Face

From an array of fonts (i.e. each font-family and its font variations to be processed), it:

  1. Validates each font-face declaration, i.e. the CSS property and value pairing. If validation fails, processing stops with no font-face styles printed.
  2. Generates the @font-face CSS for each font-family.
  3. Prints the CSS within a <style id="wp-fonts-local"> element.

The entry point into Font Face is through a new global function called wp_print_font_faces(), which is automatically called:

  • when the 'wp_head' hook runs (for the front-end).
  • when the 'admin_print_styles' hook runs (for the back-end).
  • when _wp_get_iframed_editor_assets() runs to inject the @font-face styles into the iframed editor.

Once called, it gets the fonts from Theme_JSON merged data layer, which includes theme defined fonts and user activated fonts (once the Font Library #59166 is introduced into Core).

For classic sites, themes and plugins can directly call wp_print_font_faces() and pass their fonts array to it for processing.

Deprecates _wp_theme_json_webfonts_handler().

As Font Face is a direct replacement, the stopgap code in _wp_theme_json_webfonts_handler() (introduced in 6.0.0 via [53282]) is deprecated and unused in Core.

Props note:
There's a long multiple year history baked into Font Face, which dates back to the early versions of a web font API (see #46370 and roadmap. The props list includes those who contributed from those early versions up to this commit.

References:

Follow-up to [53282].

Props aristath, jonoaldersonwp, hellofromTonya, andraganescu, annezazu, antonvlasenko, arena, askdesign, azaozz, bph, bradley2083, colorful-tones, costdev, davidbaumwald, desrosj, dingo_d, djcowan, domainsupport, dryanpress, elmastudio, flixos90, francina, garrett-eclipse, gigitux, grantmkin, grapplerulrich, gziolo, ironprogrammer, jb510, jeffpaul, jeremyyip, jffng, joostdevalk, jorgefilipecosta, juanmaguitar, mamaduka, matveb, mburridge, mitogh, ndiego, ntsekouras, oandregal, ocean90, oglekler, paaljoachim, pagelab, peterwilsoncc, poena, priethor, scruffian, SergeyBiryukov, shiloey, simison, skorasaurus, soean, westonruter, wildworks, zaguiini.
Fixes #59165.

Note: See TracTickets for help on using tickets.