Skip to content

Commit

Permalink
More fleshed out fix for fonts not loading in Site Editor.
Browse files Browse the repository at this point in the history
Previously, the theme had hardcoded the CSS for font faces to ensure they were loaded in the editor. This change adds a new `FontFaceResolver` class (forked from Core WP) to better handle this.

See: WordPress/gutenberg#59965
  • Loading branch information
justintadlock committed Aug 13, 2024
1 parent 68faac4 commit 38d1138
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 63 deletions.
96 changes: 33 additions & 63 deletions src/Editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@

namespace X3P0\Ideas;

use WP_Block_Editor_Context;
use X3P0\Ideas\Contracts\Bootable;
use X3P0\Ideas\Tools\HookAnnotation;
use X3P0\Ideas\Tools\{FontFaceResolver, HookAnnotation};

class Editor implements Bootable
{
Expand All @@ -34,6 +33,17 @@ public function boot(): void
$this->hookMethods();
}

/**
* Runs actions only when viewing the Site Editor screen.
*
* @hook load-site-editor.php
* @since 1.0.0
*/
public function loadSiteEditor(): void
{
add_action('enqueue_block_assets', [$this, 'enqueueFonts']);
}

/**
* Add editor stylesheets.
*
Expand Down Expand Up @@ -79,23 +89,33 @@ public function enqueueAssets(): void
wp_set_script_translations('x3p0-ideas-editor', 'x3p0-ideas');
}

/**
* Enqueues local web fonts. This is necessary to fix the broken Site
* Editor preview with style variations in WordPress.
*
* @since 1.0.0
* @link https://github.com/WordPress/gutenberg/issues/59965
*/
public function enqueueFonts(): void
{
$fonts = FontFaceResolver::getFonts();

ob_start();
wp_print_font_faces($fonts);
$content = ob_get_clean();

wp_register_style('x3p0-ideas-fonts', false);
wp_add_inline_style('x3p0-ideas-fonts', trim(strip_tags($content)));
wp_enqueue_style('x3p0-ideas-fonts');
}

/**
* Customizes the block editor settings.
*
* @hook block_editor_settings_all last
* @since 1.0.0
*/
public function registerSettings(
array $settings,
WP_Block_Editor_Context $context
): array {
// Load all theme fonts on the Site Editor screen.
if ('core/edit-site' === $context->name) {
$settings['styles'][] = [
'css' => $this->getFontCss()
];
}

public function registerSettings(array $settings ): array {
$settings['imageDefaultSize'] = 'x3p0-wide';
$settings['fontLibraryEnabled'] = false;

Expand All @@ -105,54 +125,4 @@ public function registerSettings(

return $settings;
}

/**
* Quick and hacky method of loading all fonts.
*
* @since 1.0.0
* @todo Create a more functional method of loading these.
*/
private function getFontCss(): string
{
$path = get_parent_theme_file_uri();
return "
@font-face{font-family:Jost;font-style:normal;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/jost/jost.woff2') format('woff2');font-feature-settings:'tnum', 'ss01' 1;font-stretch:normal;}
@font-face{font-family:Jost;font-style:italic;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/jost/jost-italic.woff2') format('woff2');font-feature-settings:'tnum', 'ss01' 1;font-stretch:normal;}
@font-face{font-family:'Roboto Flex';font-style:normal;font-weight:100 1000;font-display:fallback;src:url('{$path}/public/fonts/roboto/roboto-flex.woff2') format('woff2');font-stretch:25% 151%;}
@font-face{font-family:'Roboto Slab';font-style:normal;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/roboto/roboto-slab.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Playfair;font-style:normal;font-weight:200 700;font-display:fallback;src:url('{$path}/public/fonts/playfair/playfair.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Playfair;font-style:italic;font-weight:200 700;font-display:fallback;src:url('{$path}/public/fonts/playfair/playfair-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Dancing Script';font-style:normal;font-weight:400 700;font-display:fallback;src:url('{$path}/public/fonts/dancing-script/dancing-script.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Cabin;font-style:normal;font-weight:400 700;font-display:fallback;src:url('{$path}/public/fonts/cabin/cabin.woff2') format('woff2');font-stretch:75% 100%;}
@font-face{font-family:Cabin;font-style:italic;font-weight:400 700;font-display:fallback;src:url('{$path}/public/fonts/cabin/cabin-italic.woff2') format('woff2');font-stretch:75% 100%;}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:200 800;font-display:fallback;src:url('{$path}/public/fonts/plus-jakarta-sans/plus-jakarta-sans.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Plus Jakarta Sans';font-style:italic;font-weight:200 800;font-display:fallback;src:url('{$path}/public/fonts/plus-jakarta-sans/plus-jakarta-sans-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:100 800;font-display:fallback;src:url('{$path}/public/fonts/jetbrains-mono/jetbrains-mono.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'JetBrains Mono';font-style:italic;font-weight:100 800;font-display:fallback;src:url('{$path}/public/fonts/jetbrains-mono/jetbrains-mono-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Source Serif';font-style:normal;font-weight:200 900;font-display:fallback;src:url('{$path}/public/fonts/source/source-serif.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Source Serif';font-style:italic;font-weight:200 900;font-display:fallback;src:url('{$path}/public/fonts/source/source-serif-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Playfair Display';font-style:normal;font-weight:400 900;font-display:fallback;src:url('{$path}/public/fonts/playfair/playfair-display.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Playfair Display';font-style:italic;font-weight:400 900;font-display:fallback;src:url('{$path}/public/fonts/playfair/playfair-display-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Source Sans';font-style:normal;font-weight:200 900;font-display:fallback;src:url('{$path}/public/fonts/source/source-sans.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Source Sans';font-style:italic;font-weight:200 900;font-display:fallback;src:url('{$path}/public/fonts/source/source-sans-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Satisfy;font-style:normal;font-weight:400;font-display:fallback;src:url('{$path}/public/fonts/satisfy/satisfy.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Montserrat;font-style:normal;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/montserrat/montserrat.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Montserrat;font-style:italic;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/montserrat/montserrat-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Caveat;font-style:normal;font-weight:400 700;font-display:fallback;src:url('{$path}/public/fonts/caveat/caveat.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Raleway;font-style:normal;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/raleway/raleway.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Raleway;font-style:italic;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/raleway/raleway-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/work-sans/work-sans.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:fallback;src:url('{$path}/public/fonts/work-sans/work-sans-italic.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Roboto Serif';font-style:normal;font-weight:100 900;font-display:fallback;src:url('{$path}/experimental/fonts/roboto/roboto-serif.woff2') format('woff2');font-stretch:100%;}
@font-face{font-family:'Roboto Serif';font-style:italic;font-weight:100 900;font-display:fallback;src:url('{$path}/experimental/fonts/roboto/roboto-serif-italic.woff2') format('woff2');font-stretch:100%;}
@font-face{font-family:Oswald;font-style:normal;font-weight:200 700;font-display:fallback;src:url('{$path}/public/fonts/oswald/oswald.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Permanent Marker';font-style:normal;font-weight:400;font-display:fallback;src:url('{$path}/public/fonts/permanent-marker/permanent-marker.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Send Flowers';font-style:normal;font-weight:400;font-display:fallback;src:url('{$path}/public/fonts/send-flowers/send-flowers.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Quicksand;font-style:normal;font-weight:300 700;font-display:fallback;src:url('{$path}/public/fonts/quicksand/quicksand.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Elsie Swash Caps';font-style:normal;font-weight:400;font-display:fallback;src:url('{$path}/public/fonts/elsie/elsie-swash-caps.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:'Elsie Swash Caps';font-style:normal;font-weight:900;font-display:fallback;src:url('{$path}/public/fonts/elsie/elsie-swash-caps-900.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Lora;font-style:normal;font-weight:400 700;font-display:fallback;src:url('{$path}/x3p0-ideas/public/fonts/lora/lora.woff2') format('woff2');font-stretch:normal;}
@font-face{font-family:Lora;font-style:italic;font-weight:400 700;font-display:fallback;src:url('{$path}/x3p0-ideas/public/fonts/lora/lora.woff2') format('woff2');font-stretch:normal;}
";
}
}
186 changes: 186 additions & 0 deletions src/Tools/FontFaceResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

/**
* The Font Face Resolver class is a fork of `WP_Font_Face_Resolver` and is
* necessary for getting and parsing font-face definitions from `*.json` files
* in WordPress. This class is used as part of a solution to solve an existing
* issue with Core/Gutenberg where fonts from style variations are not loaded at
* all, creating a poor user experience.
*
* Additionally, it's pretty much impossible to sub-class the core WordPress
* `WP_Font_Face_Resolver` class because it declares nearly every method as
* `private`.
*
* @link https://github.com/WordPress/gutenberg/issues/59965
* @link https://developer.wordpress.org/reference/classes/wp_font_face_resolver/
*
* @author Justin Tadlock <[email protected]>
* @copyright Copyright (c) 2023-2024, Justin Tadlock
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL-3.0-or-later
* @link https://github.com/x3p0-dev/x3p0-ideas
*/

declare(strict_types=1);

namespace X3P0\Ideas\Tools;

use WP_Theme_JSON_Resolver;

class FontFaceResolver
{
/**
* Returns an array of font definitions to be plugged into functions
* like `wp_print_font_faces()` for enqueueing font stylesheets.
*
* @since 1.0.0
*/
public static function getFonts(): array
{
$families = [];
$settings = wp_get_global_settings();
$variations = WP_Theme_JSON_Resolver::get_style_variations();

// Loop through and store each variation's font families.
foreach ($variations as $variation) {
if (empty($variation['settings']['typography']['fontFamilies'])) {
continue;
}

foreach ($variation['settings']['typography']['fontFamilies'] as $group) {
$families = array_merge($families, $group);
}
}

// Bail early if there are no defined font families.
if ([] === $families) {
return [];
}

$fonts = static::parseFamilies($families);

// Because Core could be potentially loading a font that's
// already defined in our variations, let's remove those to
// avoid loading them a second time.
if (! empty($settings['typography']['fontFamilies'])) {
$global_families = [];

foreach ($settings['typography']['fontFamilies'] as $group) {
$global_families = array_merge($global_families, $group);
}

if ([] !== $global_families) {
$fonts = array_diff_key(
$fonts,
static::parseFamilies($global_families)
);
}
}

return $fonts;
}

/**
* Parses the an array of font families and grabs any font-face
* definitions from them.
*
* @since 1.0.0
*/
private static function parseFamilies(array $families): array
{
$faces = [];

foreach ($families as $family) {
if (! isset($family['fontFace']) || ! isset($family['fontFamily'])) {
continue;
}

$name = static::parseName($family['fontFamily']);

if (! $name || isset($faces[$name])) {
continue;
}

$faces[$name] = static::convertProperties($family['fontFace'], $name);
}

// Just a little nicer sorting. 😊
ksort($faces);

return $faces;
}

/**
* Parses a font-family name. Ensures that we only use the first name if
* presented a comma-separated list.
*
* @since 1.0.0
*/
private static function parseName(string $family): string
{
if (str_contains($family, ',')) {
$family = explode(',', $family)[0];
}

return trim($family, "\"'");
}

/**
* Ensures that font-face properties are converted to a form that can be
* used in CSS.
*
* @since 1.0.0
*/
private static function convertProperties(array $definitions, string $name): array
{
$converted = [];

foreach ($definitions as $properties) {
$properties['font-family'] = $name;

if (! empty($properties['src'])) {
$properties['src'] = static::toThemeFileUri(
(array) $properties['src']
);
}

$converted[] = static::toKebabCase($properties);
}

return $converted;
}

/**
* Replaces file URI references from JSON to the theme file URI.
*
* @since 1.0.0
*/
private static function toThemeFileUri(array $src): array
{
$placeholder = 'file:./';

foreach ($src as $key => $url) {
if (str_starts_with($url, $placeholder)) {
$src[ $key ] = get_theme_file_uri(
str_replace($placeholder, '', $url)
);
}
}

return $src;
}

/**
* Converts property names/keys to kebab-case.
*
* @since 1.0.0
*/
private static function toKebabCase(array $data): array
{
foreach ($data as $key => $value) {
unset($data[$key]);
$data[ _wp_to_kebab_case($key) ] = $value;
}

return $data;
}
}

0 comments on commit 38d1138

Please sign in to comment.