diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js
index a59ac310bcd303..8af3b493e44966 100644
--- a/packages/block-editor/src/components/editor-styles/index.js
+++ b/packages/block-editor/src/components/editor-styles/index.js
@@ -18,6 +18,7 @@ import { useSelect } from '@wordpress/data';
import transformStyles from '../../utils/transform-styles';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';
+import useEditorFontsResolver from '../use-editor-fonts-resolver';
extend( [ namesPlugin, a11yPlugin ] );
@@ -105,6 +106,9 @@ function EditorStyles( { styles, scope, transformOptions } ) {
+
+
+
{ transformedStyles.map( ( css, index ) => (
) ) }
diff --git a/packages/block-editor/src/components/use-editor-fonts-resolver/index.js b/packages/block-editor/src/components/use-editor-fonts-resolver/index.js
new file mode 100644
index 00000000000000..a63111614b23ec
--- /dev/null
+++ b/packages/block-editor/src/components/use-editor-fonts-resolver/index.js
@@ -0,0 +1,71 @@
+/**
+ * WordPress dependencies
+ */
+import { useState, useMemo, useCallback } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { getDisplaySrcFromFontFace, loadFontFaceInBrowser } from './utils';
+import { store as editorStore } from '../../store';
+
+function useEditorFontsResolver() {
+ const [ loadedFontUrls, setLoadedFontUrls ] = useState( new Set() );
+
+ const { currentTheme = {}, fontFamilies = [] } = useSelect( ( select ) => {
+ return {
+ currentTheme:
+ select( editorStore ).getSettings()?.__experimentalFeatures
+ ?.currentTheme,
+ fontFamilies:
+ select( editorStore ).getSettings()?.__experimentalFeatures
+ ?.typography?.fontFamilies,
+ };
+ }, [] );
+
+ const fontFaces = useMemo( () => {
+ return Object.values( fontFamilies )
+ .flat()
+ .map( ( family ) => family.fontFace )
+ .filter( Boolean )
+ .flat();
+ }, [ fontFamilies ] );
+
+ const loadFontFaceAsset = useCallback(
+ async ( fontFace, ownerDocument ) => {
+ if ( ! fontFace.src ) {
+ return;
+ }
+
+ const src = getDisplaySrcFromFontFace(
+ fontFace.src,
+ currentTheme?.stylesheet_uri
+ );
+
+ if ( ! src || loadedFontUrls.has( src ) ) {
+ return;
+ }
+
+ loadFontFaceInBrowser( fontFace, src, ownerDocument );
+ setLoadedFontUrls( ( prevUrls ) => new Set( prevUrls ).add( src ) );
+ },
+ [ currentTheme, loadedFontUrls ]
+ );
+
+ return useCallback(
+ ( node ) => {
+ if ( ! node ) {
+ return;
+ }
+
+ const { ownerDocument } = node;
+ fontFaces.forEach( ( fontFace ) =>
+ loadFontFaceAsset( fontFace, ownerDocument )
+ );
+ },
+ [ fontFaces, loadFontFaceAsset ]
+ );
+}
+
+export default useEditorFontsResolver;
diff --git a/packages/block-editor/src/components/use-editor-fonts-resolver/utils.js b/packages/block-editor/src/components/use-editor-fonts-resolver/utils.js
new file mode 100644
index 00000000000000..467eb4ef82a01a
--- /dev/null
+++ b/packages/block-editor/src/components/use-editor-fonts-resolver/utils.js
@@ -0,0 +1,103 @@
+/*
+ * Format the font face name to use in the font-family property of a font face.
+ *
+ * The input can be a string with the font face name or a string with multiple font face names separated by commas.
+ * It removes the leading and trailing quotes from the font face name.
+ *
+ * @param {string} input - The font face name.
+ * @return {string} The formatted font face name.
+ *
+ * Example:
+ * formatFontFaceName("Open Sans") => "Open Sans"
+ * formatFontFaceName("'Open Sans', sans-serif") => "Open Sans"
+ * formatFontFaceName(", 'Open Sans', 'Helvetica Neue', sans-serif") => "Open Sans"
+ */
+function formatFontFaceName( input ) {
+ if ( ! input ) {
+ return '';
+ }
+
+ let output = input.trim();
+ if ( output.includes( ',' ) ) {
+ output = output
+ .split( ',' )
+ // Finds the first item that is not an empty string.
+ .find( ( item ) => item.trim() !== '' )
+ .trim();
+ }
+ // Removes leading and trailing quotes.
+ output = output.replace( /^["']|["']$/g, '' );
+
+ // Firefox needs the font name to be wrapped in double quotes meanwhile other browsers don't.
+ if ( window.navigator.userAgent.toLowerCase().includes( 'firefox' ) ) {
+ output = `"${ output }"`;
+ }
+ return output;
+}
+
+/*
+ * Loads the font face from a URL and adds it to the browser.
+ * It also adds it to the iframe document.
+ */
+export async function loadFontFaceInBrowser( fontFace, source, documentRef ) {
+ if ( typeof source !== 'string' ) {
+ return;
+ }
+ const dataSource = `url(${ source })`;
+ const newFont = new window.FontFace(
+ formatFontFaceName( fontFace.fontFamily ),
+ dataSource,
+ {
+ style: fontFace.fontStyle,
+ weight: fontFace.fontWeight,
+ }
+ );
+
+ const loadedFace = await newFont.load();
+
+ // Add the font to the ref document.
+ documentRef.fonts.add( loadedFace );
+
+ // Add the font to the window document.
+ if ( documentRef !== window.document ) {
+ window.document.fonts.add( loadedFace );
+ }
+}
+
+function isUrlEncoded( url ) {
+ if ( typeof url !== 'string' ) {
+ return false;
+ }
+ return url !== decodeURIComponent( url );
+}
+
+/*
+ * Retrieves the display source from a font face src.
+ *
+ * @param {string|string[]} fontSrc - The font face src.
+ * @param {string} baseUrl - The base URL to resolve the src.
+ * @return {string|undefined} The display source or undefined if the input is invalid.
+ */
+export function getDisplaySrcFromFontFace( fontSrc, baseUrl ) {
+ if ( ! fontSrc ) {
+ return;
+ }
+
+ let src;
+ if ( Array.isArray( fontSrc ) ) {
+ src = fontSrc[ 0 ];
+ } else {
+ src = fontSrc;
+ }
+
+ if ( ! isUrlEncoded( src ) ) {
+ src = encodeURI( src );
+ }
+
+ // If baseUrl is provided, use it to resolve the src.
+ if ( src.startsWith( 'file:.' ) ) {
+ src = baseUrl + '/' + src.replace( 'file:./', '' );
+ }
+
+ return src;
+}
diff --git a/packages/edit-site/src/components/global-styles-renderer/index.js b/packages/edit-site/src/components/global-styles-renderer/index.js
index 2e840a7acdc375..0b65627a7bb443 100644
--- a/packages/edit-site/src/components/global-styles-renderer/index.js
+++ b/packages/edit-site/src/components/global-styles-renderer/index.js
@@ -4,6 +4,7 @@
import { useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
+import { store as coreDataStore } from '@wordpress/core-data';
/**
* Internal dependencies
@@ -15,12 +16,18 @@ import { TEMPLATE_POST_TYPE } from '../../utils/constants';
const { useGlobalStylesOutput } = unlock( blockEditorPrivateApis );
function useGlobalStylesRenderer() {
- const postType = useSelect( ( select ) => {
- return select( editSiteStore ).getEditedPostType();
+ const { postType, currentTheme } = useSelect( ( select ) => {
+ return {
+ postType: select( editSiteStore ).getEditedPostType(),
+ currentTheme: select( coreDataStore ).getCurrentTheme(),
+ };
} );
const [ styles, settings ] = useGlobalStylesOutput(
postType !== TEMPLATE_POST_TYPE
);
+
+ settings.currentTheme = currentTheme;
+
const { getSettings } = useSelect( editSiteStore );
const { updateSettings } = useDispatch( editSiteStore );