PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.11.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.11.1
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
ablocks / includes / classes / core-font-registry.php

core-font-registry.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.11.1, at includes/classes/core-font-registry.php

186 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Classes;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * Phase 2: hand font emission to WordPress core.
10 *
11 * Locally-hosted fonts the current page needs are injected into theme.json
12 * (settings.typography.fontFamilies). Core's wp_print_font_faces() then prints
13 * the @font-face rules automatically — no custom enqueue/discovery.
14 *
15 * Any font not yet self-hosted falls back to a Google Fonts <link>.
16 *
17 * Requires WP 6.5+ (the plugin requires 6.8). See docs/FONT-MANAGEMENT-PLAN.md.
18 */
19 class CoreFontRegistry {
20
21 public static function init() {
22 $self = new self();
23 // Inject the page's local fonts so core prints their @font-face. Register
24 // on a single origin to avoid emitting duplicate @font-face rules.
25 add_filter( 'wp_theme_json_data_theme', [ $self, 'register_page_fonts' ] );
26 // Remote fallback for fonts not yet downloaded locally.
27 add_action( 'wp_enqueue_scripts', [ $self, 'enqueue_remote_fallback' ], 999 );
28 // Metric-adjusted local faces, so the pre-swap render is the right size.
29 add_action( 'wp_enqueue_scripts', [ $self, 'enqueue_fallback_faces' ], 5 );
30 }
31
32 /**
33 * Emit the metric-adjusted fallback @font-face rules for this page's fonts.
34 *
35 * These reference locally installed system fonts via local() — nothing is
36 * downloaded. Their only job is to make the fallback render occupy exactly the
37 * space the real font will, so the swap doesn't shift the layout.
38 */
39 public function enqueue_fallback_faces() {
40 $fonts = $this->page_fonts();
41 if ( empty( $fonts ) ) {
42 return;
43 }
44
45 $css = FontStack::get_fallback_face_css( $fonts );
46 if ( '' === $css ) {
47 return;
48 }
49
50 wp_register_style( 'ablocks-font-fallbacks', false, [], ABLOCKS_VERSION );
51 wp_enqueue_style( 'ablocks-font-fallbacks' );
52 wp_add_inline_style( 'ablocks-font-fallbacks', $css );
53 }
54
55 /**
56 * Page fonts, resolved once per request.
57 */
58 protected function page_fonts() {
59 return FontCollector::get_page_fonts();
60 }
61
62 /**
63 * Register the current page's locally-hosted fonts into theme.json data so
64 * core emits their @font-face declarations.
65 *
66 * @param \WP_Theme_JSON_Data $theme_json
67 * @return \WP_Theme_JSON_Data
68 */
69 public function register_page_fonts( $theme_json ) {
70 if ( is_admin() || ! is_object( $theme_json ) || ! method_exists( $theme_json, 'get_data' ) ) {
71 return $theme_json;
72 }
73
74 $fonts = $this->page_fonts();
75 if ( empty( $fonts ) ) {
76 return $theme_json;
77 }
78
79 $loader = new FontLoadLocally();
80 $families = [];
81
82 foreach ( $fonts as $family => $weights ) {
83 $local_faces = $loader->get_local_font_faces( $family, (array) $weights );
84 if ( empty( $local_faces ) ) {
85 continue; // not self-hosted yet → handled by the remote fallback
86 }
87
88 $font_faces = [];
89 foreach ( $local_faces as $face ) {
90 $font_faces[] = [
91 'fontFamily' => $family,
92 'fontStyle' => 'normal',
93 'fontWeight' => $face['weight'],
94 'fontDisplay' => 'swap',
95 'src' => [ $face['src'] ],
96 ];
97 }
98
99 $families[] = [
100 'fontFamily' => $family,
101 'name' => $family,
102 'slug' => sanitize_title( $family ),
103 'fontFace' => $font_faces,
104 ];
105 }
106
107 if ( empty( $families ) ) {
108 return $theme_json;
109 }
110
111 $data = $theme_json->get_data();
112
113 // WP 6.6+ keys typography.fontFamilies by origin ( [ 'theme' => [ …presets… ] ] );
114 // older versions used a flat preset list. Append our families to the 'theme'
115 // origin bucket. Merging a flat list onto the origin-keyed shape (the previous
116 // behaviour) produced a malformed preset with no 'slug', tripping core's
117 // get_settings_values_by_slug()/get_settings_slugs() (undefined array key "slug").
118 $existing = $data['settings']['typography']['fontFamilies'] ?? [];
119 if ( ! is_array( $existing ) ) {
120 $existing = [];
121 }
122 if ( isset( $existing[0] ) ) { // flat list → normalise to the theme origin
123 $existing = [ 'theme' => $existing ];
124 }
125 $existing['theme'] = array_merge(
126 ( isset( $existing['theme'] ) && is_array( $existing['theme'] ) ) ? $existing['theme'] : [],
127 $families
128 );
129 $data['settings']['typography']['fontFamilies'] = $existing;
130
131 // update_with() replaces the theme origin's preset list wholesale, so $existing
132 // must already contain the theme's own fonts alongside ours.
133 return $theme_json->update_with( $data );
134 }
135
136 /**
137 * Enqueue a Google Fonts <link> for any page font not available locally.
138 */
139 public function enqueue_remote_fallback() {
140 $fonts = $this->page_fonts();
141 if ( empty( $fonts ) ) {
142 return;
143 }
144
145 $missing = ( new FontLoadLocally() )->get_missing( $fonts );
146 if ( empty( $missing ) ) {
147 return;
148 }
149
150 $family_strings = [];
151 foreach ( $missing as $family => $weights ) {
152 $weights = array_unique( array_map( 'strval', (array) $weights ) );
153 sort( $weights );
154 $encoded = str_replace( ' ', '+', $family );
155 $family_strings[] = ! empty( $weights ) ? $encoded . ':wght@' . implode( ';', $weights ) : $encoded;
156 }
157
158 $url = 'https://fonts.googleapis.com/css2?family=' . implode( '&family=', $family_strings ) . '&display=swap';
159 wp_enqueue_style( 'ablocks-google-fonts', esc_url( $url ), [], null );
160
161 // Warm up the font connections early so the render-blocking stylesheet +
162 // its font files resolve faster (helps FCP/LCP). Only added when a remote
163 // font is actually loaded.
164 add_filter( 'wp_resource_hints', [ $this, 'preconnect_google_fonts' ], 10, 2 );
165 }
166
167 /**
168 * Add preconnect hints for the Google Fonts origins.
169 *
170 * @param array $hints Resource hint URLs/attributes for this relation.
171 * @param string $relation Current relation type (preconnect, dns-prefetch, …).
172 * @return array
173 */
174 public function preconnect_google_fonts( $hints, $relation ) {
175 if ( 'preconnect' !== $relation ) {
176 return $hints;
177 }
178 $hints[] = [ 'href' => 'https://fonts.googleapis.com' ];
179 $hints[] = [
180 'href' => 'https://fonts.gstatic.com',
181 'crossorigin' => 'anonymous',
182 ];
183 return $hints;
184 }
185 }
186