From 38d11389a5bc01520b7e714be40226d323d9f53e Mon Sep 17 00:00:00 2001 From: Justin Tadlock Date: Tue, 13 Aug 2024 12:26:32 -0500 Subject: [PATCH] More fleshed out fix for fonts not loading in Site Editor. 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: https://github.com/WordPress/gutenberg/issues/59965 --- src/Editor.php | 96 ++++++----------- src/Tools/FontFaceResolver.php | 186 +++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 63 deletions(-) create mode 100644 src/Tools/FontFaceResolver.php diff --git a/src/Editor.php b/src/Editor.php index 56fa527f..357c2f02 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -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 { @@ -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. * @@ -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; @@ -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;} - "; - } } diff --git a/src/Tools/FontFaceResolver.php b/src/Tools/FontFaceResolver.php new file mode 100644 index 00000000..984bd2f5 --- /dev/null +++ b/src/Tools/FontFaceResolver.php @@ -0,0 +1,186 @@ + + * @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; + } +}