Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load variation font faces with theme relative urls using backend provided full urls (_links) #65019

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6219a6d
add editor font face resolver component
matiasbenedetto Sep 3, 2024
1e8ef55
remove code not needed
matiasbenedetto Sep 4, 2024
65f3754
default value when font families aren't defined
matiasbenedetto Sep 5, 2024
77f7638
comments formatting
matiasbenedetto Sep 9, 2024
0bed01d
use callback for loadFontFaceAsset
matiasbenedetto Sep 9, 2024
48bfe65
improve syntax
matiasbenedetto Sep 9, 2024
65d147a
Merge branch 'trunk' into add/editor-font-face-resolver
matiasbenedetto Sep 11, 2024
c6a32f9
Move EditorFontsResolver inside EditorStyles, use fontFamilies data f…
matiasbenedetto Sep 13, 2024
5a9e2d0
use a ref to reference the current document
matiasbenedetto Sep 13, 2024
27b5e64
currentTheme default to empty object
matiasbenedetto Sep 16, 2024
463fa95
revert changes on useDarkThemeBodyClassName, refactor useEditorFontsR…
matiasbenedetto Sep 18, 2024
9bd3378
revert not needed change
matiasbenedetto Sep 18, 2024
5cecd18
Merge branch 'trunk' into add/editor-font-face-resolver
matiasbenedetto Sep 20, 2024
c262896
Merge branch 'trunk' into add/editor-font-face-resolver
matiasbenedetto Sep 20, 2024
8ced871
try adding currentTheme to the editor settings
matiasbenedetto Sep 20, 2024
ee98b6e
add theme fonts uris
matiasbenedetto Sep 26, 2024
1461989
use _links in the font resolver
matiasbenedetto Sep 27, 2024
8172039
Merge branch 'trunk' into add/editor-font-face-resolver
matiasbenedetto Sep 30, 2024
7cb4c01
Fix PHP linting
getdave Oct 7, 2024
20a00e3
Use correct alias
getdave Oct 7, 2024
f2ad8e7
load the fonts as styles
matiasbenedetto Oct 9, 2024
5a3b172
optional chain
matiasbenedetto Oct 9, 2024
db89bf6
remove default not needed
matiasbenedetto Oct 9, 2024
32868cd
use array_merge instead of +
matiasbenedetto Oct 9, 2024
efd9562
get the theme font uris inside get_resolved_theme_uris function
matiasbenedetto Oct 9, 2024
905f0d8
Merge branch 'trunk' into add/editor-font-face-resolver
matiasbenedetto Oct 9, 2024
e85076a
remove unwanted changes
matiasbenedetto Oct 9, 2024
a1cdf2c
remove unwwanted changes
matiasbenedetto Oct 9, 2024
2250a0c
lint
matiasbenedetto Oct 9, 2024
59ce5a4
lint
matiasbenedetto Oct 9, 2024
d94e2f2
Adding a unit test to cover the font file resolution in WP_Theme_JSON…
ramonjd Oct 11, 2024
607740a
update comment
matiasbenedetto Oct 14, 2024
b69ae26
add function comment
matiasbenedetto Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/block-editor/src/components/editor-styles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ] );

Expand Down Expand Up @@ -105,6 +106,9 @@ function EditorStyles( { styles, scope, transformOptions } ) {
<style
ref={ useDarkThemeBodyClassName( transformedStyles, scope ) }
/>

<style ref={ useEditorFontsResolver() } />

{ transformedStyles.map( ( css, index ) => (
<style key={ index }>{ css }</style>
) ) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* 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';
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved

function useEditorFontsResolver() {
const [ loadedFontUrls, setLoadedFontUrls ] = useState( new Set() );

const { currentTheme = {}, fontFamilies = [] } = useSelect( ( select ) => {
return {
currentTheme:
// Disable Reason: Using 'core' as string to avoid circular dependency importing from @wordpress/core-data.
// eslint-disable-next-line @wordpress/data-no-store-string-literals
select( 'core' )?.getCurrentTheme(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not allowed in the block-editor package. We shouldn't add an eslint exception. Why do we need the theme object here?

Copy link
Contributor Author

@matiasbenedetto matiasbenedetto Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get the base url of the theme's fonts.

Example: the theme.json data has something like file:/assets/fonts/one-font.woff2.
We need to know how to resolve that theme relative font URL in the browser.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to find an alternative solution. The block editor is a generic package independent of WordPress and can't make REST API calls.

The alternative I suggested initially is to "resolve" the files in the server and add a new _unstableType for the "styles" object received by the editor.

Copy link
Contributor Author

@matiasbenedetto matiasbenedetto Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to "resolve" the files in the server

Do you mean when the theme.json data is read from the file / database?

add a new _unstableType for the "styles" object received by the editor.

Sorry but I don't understand this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean when the theme.json data is read from the file / database?

Yes, basically in this place, in the code base. We read global styles and enqueue "styles" setting in the block editor. We could add a new "style" in that array with a specific "_unstableType" => "font" that we treat in a custom way in the EditorStyles component.

https://github.com/WordPress/wordpress-develop/blob/bf09fe506620678fb82c3b872309edda0ed8ce61/src/wp-includes/block-editor.php#L497-L553

I know that function is in WordPress Core but in Gutenberg we have a hook to override the output of this function block_editor_settings_all filter I think.


I know that we used this approach in the past for "svg filters" which are also "special styles" that are not just CSS. @ajlende would know more but I think we moved a little bit from it (not entirely sure why though)

Copy link
Contributor Author

@matiasbenedetto matiasbenedetto Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to follow the pattern of this PR using the _links array. It seems to work with the regular theme.json, but I'm unable to access the links for the style variations. How I can pass the variation _settings to the editor settings?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand the use case. What do we need to do with the resolved font paths? Preload the fonts? Could you provide some testing steps and expectations for style variations?

Just to confirm, if I run await wp.data.resolveSelect( 'core' ).__experimentalGetCurrentThemeGlobalStylesVariations() in the console I see that the variations' fonts are resolved in the _links object.

How I can pass the variation _settings to the editor settings?

Aren't the links already available in the settings?

It's private, so, in the useEditorFontsResolver useSelect you'd have to import the private symbol key (import { globalStylesLinksDataKey } from '../../store/private-keys';) and access it like settings[ globalStylesLinksDataKey ].

I just logged the settings out in that hook and I can see the font _links object, and when I change variations the links update.

I'm not sure that helps, maybe it leads you in the right direction?


Just to clarify and contrast what I was working on with background images, the resolved background image paths are used to build the correct CSS. It's done at this point in the global styles output generation:

export function getStylesDeclarations(
blockStyles = {},
selector = '',
useRootPaddingAlign,
tree = {},
disableRootPadding = false
) {

tree contains the embedded _links object and path resolution is done "on the fly" when outputting CSS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we need to do with the resolved font paths? Preload the fonts? Could you provide some testing steps and expectations for style variations?

This is the issue we are trying to fix: #59965
(load the fonts in the main frame and in the editor iframes when the fonts are defined in variations)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this commit I added the JS consumption of the _links. It seems to work for the main editor but not for the editors loaded to display the style variations.

The commit includes a console.log to display the font families and the _links.

const { _links = [], fontFamilies = [] } = useSelect( ( select ) => {
const { getSettings } = select( editorStore );
const _settings = getSettings();
return {
_links: _settings[ globalStylesLinksDataKey ]?.[ 'wp:theme-file' ],
fontFamilies:
_settings?.__experimentalFeatures?.typography?.fontFamilies,
};
}, [] );
// eslint-disable-next-line no-console
console.log( 'fontFamilies', fontFamilies, 'links', _links );

In the screencast you can see how the editors instances created to display the style variations doesn´t get the right list of font families (the ones from the style variation) and the _links array is empty.

Screencast.from.27-09-24.14.04.29.webm

Is there any way to get the right list of font families and _links in the editors used to feature a style variation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like here you need all the variations' fonts at once for the sidebar. Is that right?

If you look at where the key value is set in the settings, you'll notice that the _links passed to settings contains only the resolved links for the merged global styles that are currently loaded in the editor.

That's why the resolved paths for style variations only work once you click on them, because it's it that point they're loaded into the editor.

So you'll probably need to iterate over the output of await wp.data.resolveSelect( 'core' ).__experimentalGetCurrentThemeGlobalStylesVariations() as hinted at above since they contain all the variations their resolved paths, and somehow preload the fonts for each variation.

However you can't use these core selectors in the block editor package, that is, in useEditorFontsResolver().

Rather, you might like to tackle from the edit-site package, where the style variations are rendered.

For example, export useEditorFontsResolver() as a private method and have it accept _links and fontFamilies as dependencies. I don't know if that will work, but it's the first thing I'd try without having extra context.

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;
Original file line number Diff line number Diff line change
@@ -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();
}
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
// 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 }"`;
}
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Loading