diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js index d0a53a4acea4f8..011f09b12a841f 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js @@ -9,7 +9,7 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components'; import { FONT_WEIGHTS, FONT_STYLES } from './constants'; import { unlock } from '../../../../lock-unlock'; import { fetchInstallFontFace } from '../resolvers'; -import { formatFontFamily } from './preview-styles'; +import { formatFontFaceName } from './preview-styles'; /** * Browser dependencies @@ -99,7 +99,7 @@ export async function loadFontFaceInBrowser( fontFace, source, addTo = 'all' ) { } const newFont = new window.FontFace( - formatFontFamily( fontFace.fontFamily ), + formatFontFaceName( fontFace.fontFamily ), dataSource, { style: fontFace.fontStyle, diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/preview-styles.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/preview-styles.js index 389cebde9249af..2bd80c86493fb6 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/preview-styles.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/preview-styles.js @@ -30,22 +30,76 @@ function extractFontWeights( fontFaces ) { return result; } +/* + * Format the font family to use in the CSS font-family property of a CSS rule. + * + * The input can be a string with the font family name or a string with multiple font family names separated by commas. + * It follows the recommendations from the CSS Fonts Module Level 4. + * https://www.w3.org/TR/css-fonts-4/#font-family-prop + * + * @param {string} input - The font family. + * @return {string} The formatted font family. + * + * Example: + * formatFontFamily( "Open Sans, Font+Name, sans-serif" ) => '"Open Sans", "Font+Name", sans-serif' + * formatFontFamily( "'Open Sans', sans-serif" ) => '"Open Sans", sans-serif' + * formatFontFamily( "DotGothic16, Slabo 27px, serif" ) => '"DotGothic16","Slabo 27px",serif' + * formatFontFamily( "Mine's, Moe's Typography" ) => `"mine's","Moe's Typography"` + */ export function formatFontFamily( input ) { - return input - .split( ',' ) - .map( ( font ) => { - font = font.trim(); // Remove any leading or trailing white spaces - // If the font doesn't start with quotes and contains a space, then wrap in quotes. - // Check that string starts with a single or double quote and not a space - if ( - ! ( font.startsWith( '"' ) || font.startsWith( "'" ) ) && - font.indexOf( ' ' ) !== -1 - ) { - return `"${ font }"`; - } - return font; // Return font as is if no transformation is needed - } ) - .join( ', ' ); + // Matchs any non alphabetic characters (a-zA-Z), dashes - , or parenthesis () + const regex = /[^a-zA-Z\-()]+/; + const output = input.trim(); + + const formatItem = ( item ) => { + item = item.trim(); + if ( item.match( regex ) ) { + // removes leading and trailing quotes. + item = item.replace( /^["']|["']$/g, '' ); + return `"${ item }"`; + } + return item; + }; + + if ( output.includes( ',' ) ) { + return output + .split( ',' ) + .map( formatItem ) + .filter( ( item ) => item !== '' ) + .join( ', ' ); + } + + return formatItem( output ); +} + +/* + * 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" + */ +export function formatFontFaceName( input ) { + const output = input.trim(); + if ( output.includes( ',' ) ) { + return ( + output + .split( ',' ) + .find( ( item ) => item.trim() !== '' ) + .trim() + // removes leading and trailing quotes. + .replace( /^["']|["']$/g, '' ) + ); + } + // removes leading and trailing quotes. + return output.replace( /^["']|["']$/g, '' ); } export function getFamilyPreviewStyle( family ) { diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js index 0273709502a43d..fd38d740ab34b4 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js @@ -1,7 +1,11 @@ /** * Internal dependencies */ -import { getFamilyPreviewStyle, formatFontFamily } from '../preview-styles'; +import { + getFamilyPreviewStyle, + formatFontFamily, + formatFontFaceName, +} from '../preview-styles'; describe( 'getFamilyPreviewStyle', () => { it( 'should return default fontStyle and fontWeight if fontFace is not provided', () => { @@ -139,7 +143,7 @@ describe( 'formatFontFamily', () => { "Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif" ) ).toBe( - "Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif" + 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif' ); } ); @@ -153,9 +157,37 @@ describe( 'formatFontFamily', () => { ); } ); - it( 'should wrap only those font names with spaces which are not already quoted', () => { - expect( formatFontFamily( 'Baloo Bhai 2, Arial' ) ).toBe( - '"Baloo Bhai 2", Arial' + it( 'should wrap names with special characters in quotes', () => { + expect( + formatFontFamily( + 'Font+Name, Font*Name, _Font_Name_, generic(kai), sans-serif' + ) + ).toBe( + '"Font+Name", "Font*Name", "_Font_Name_", generic(kai), sans-serif' + ); + } ); + + it( 'should fix empty wrong formatted font family', () => { + expect( formatFontFamily( ', Abril Fatface,Times,serif' ) ).toBe( + '"Abril Fatface", Times, serif' + ); + } ); +} ); + +describe( 'formatFontFaceName', () => { + it( 'should remove leading and trailing quotes', () => { + expect( formatFontFaceName( '"Open Sans"' ) ).toBe( 'Open Sans' ); + } ); + + it( 'should remove leading and trailing quotes from multiple font face names', () => { + expect( + formatFontFaceName( "'Open Sans', 'Helvetica Neue', sans-serif" ) + ).toBe( 'Open Sans' ); + } ); + + it( 'should remove leading and trailing quotes even from names with spaces and special characters', () => { + expect( formatFontFaceName( "'Font+Name 24', sans-serif" ) ).toBe( + 'Font+Name 24' ); } ); } );