[wp-trac] [WordPress Trac] #46370: A proposal for creating an API to register and enqueue web fonts

WordPress Trac noreply at wordpress.org
Fri Dec 13 11:45:53 UTC 2019


#46370: A proposal for creating an API to register and enqueue web fonts
-----------------------------+-----------------------------------
 Reporter:  jonoaldersonwp   |       Owner:  (none)
     Type:  feature request  |      Status:  new
 Priority:  normal           |   Milestone:  Awaiting Review
Component:  General          |     Version:
 Severity:  normal           |  Resolution:
 Keywords:                   |     Focuses:  performance, privacy
-----------------------------+-----------------------------------
Description changed by jonoaldersonwp:

Old description:

> = 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 ([https://wordpress.org/ wordpress.org],
> the WordPress admin area, Gutenberg, and some of our [https://wp-
> themes.com/twentyseventeen/ 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
> [https://developers.google.com/web/fundamentals/performance/optimizing-
> content-efficiency/webfont-optimization 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
> [https://developers.google.com/web/fundamentals/performance/optimizing-
> content-efficiency/webfont-optimization#the_font_loading_api 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 [https://developers.google.com/web/fundamentals/performance
> /optimizing-content-efficiency/webfont-optimization Web Fundamentals
> documentation] provides an example of an optimal CSS structure for
> loading fonts. E.g.,
>
> {{{#!css
> @font-face {
>   font-family: 'Awesome Font';
>   font-style: normal;
>   font-weight: 400;
>   font-display: auto;
>   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: auto;
>   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
> <?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'                 => 'auto',
>                         '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
> <?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:
>
> {{{#!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.:
>
> {{{#!php
> wp_enqueue_font('Awesome Font');
> }}}
>
> Produces the following:
>
> {{{#!css
> @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.,
> `get_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.,
>
> {{{#!php
> /**
>  * 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
> <?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
> [https://core.trac.wordpress.org/ticket/42438 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.

New description:

 = 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 ([https://wordpress.org/ wordpress.org], the
 WordPress admin area, Gutenberg, and some of our [https://wp-
 themes.com/twentyseventeen/ 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
 [https://developers.google.com/web/fundamentals/performance/optimizing-
 content-efficiency/webfont-optimization 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
 [https://developers.google.com/web/fundamentals/performance/optimizing-
 content-efficiency/webfont-optimization#the_font_loading_api 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 [https://developers.google.com/web/fundamentals/performance
 /optimizing-content-efficiency/webfont-optimization Web Fundamentals
 documentation] provides an example of an optimal CSS structure for loading
 fonts. E.g.,

 {{{#!css
 @font-face {
   font-family: 'Awesome Font';
   font-style: normal;
   font-weight: 400;
   font-display: auto;
   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: auto;
   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
 <?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'                 => 'auto',
                         '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
 <?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:

 {{{#!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.:

 {{{#!php
 wp_enqueue_font('Awesome Font');
 }}}

 Produces the following:

 {{{#!css
 @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.,
 `get_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.,

 {{{#!php
 /**
  * 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
 <?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 [https://core.trac.wordpress.org/ticket/42438
 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.

--

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/46370#comment:23>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list