Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 25 additions & 11 deletions packages/global-styles-ui/src/font-library/upload-fonts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
ProgressBar,
} from '@wordpress/components';
import { useContext, useState } from '@wordpress/element';
import type { FontFace } from '@wordpress/core-data';

/**
* Internal dependencies
Expand All @@ -22,7 +21,21 @@ import { ALLOWED_FILE_EXTENSIONS } from './utils/constants';
import { FontLibraryContext } from './context';
import { Font } from './lib/lib-font.browser';
import makeFamiliesFromFaces from './utils/make-families-from-faces';
import { loadFontFaceInBrowser } from './utils';
import { loadFontFaceInBrowser, normalizeCSSFontFaceFontFamily } from './utils';

export interface FontFaceMetadata {
/*
* Font name for display
*/
name: string;

/**
* CSS @font-face font-family value.
*/
fontFamily: string;
fontStyle?: string | undefined;
fontWeight?: string | number | undefined;
}

function UploadFonts() {
const { installFonts } = useContext( FontLibraryContext );
Expand Down Expand Up @@ -108,11 +121,7 @@ function UploadFonts() {
const fontFacesLoaded = await Promise.all(
files.map( async ( fontFile: File ) => {
const fontFaceData = await getFontFaceMetadata( fontFile );
await loadFontFaceInBrowser(
fontFaceData,
fontFaceData.file,
'all'
);
await loadFontFaceInBrowser( fontFaceData, fontFile, 'all' );
return fontFaceData;
} )
);
Expand Down Expand Up @@ -146,7 +155,9 @@ function UploadFonts() {
} );
}

const getFontFaceMetadata = async ( fontFile: File ) => {
const getFontFaceMetadata = async (
fontFile: File
): Promise< FontFaceMetadata > => {
const buffer = await readFileAsArrayBuffer( fontFile );
const fontObj: Font & {
onload?: ( val: { detail: { font: any } } ) => void;
Expand All @@ -171,9 +182,12 @@ function UploadFonts() {
const weightRange = weightAxis
? `${ weightAxis.minValue } ${ weightAxis.maxValue }`
: null;

const cssFontFamily = normalizeCSSFontFaceFontFamily( fontName );

return {
file: fontFile,
fontFamily: fontName,
name: fontName,
fontFamily: cssFontFamily,
fontStyle: isItalic ? 'italic' : 'normal',
fontWeight: weightRange || fontWeight,
};
Expand All @@ -185,7 +199,7 @@ function UploadFonts() {
* @param {Array} fontFaces The font faces to be installed
* @return {void}
*/
const handleInstall = async ( fontFaces: FontFace[] ) => {
const handleInstall = async ( fontFaces: FontFaceMetadata[] ) => {
const fontFamilies = makeFamiliesFromFaces( fontFaces );

try {
Expand Down
49 changes: 39 additions & 10 deletions packages/global-styles-ui/src/font-library/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type { DataRegistry } from '@wordpress/data';
*/
import { FONT_WEIGHTS, FONT_STYLES } from './constants';
import { fetchInstallFontFace } from '../api';
import { formatFontFaceName } from './preview-styles';
import type { FontFamilyToUpload, FontUploadResult } from '../types';
import { unlock } from '../../lock-unlock';

Expand Down Expand Up @@ -114,14 +113,10 @@ export async function loadFontFaceInBrowser(
return;
}

const newFont = new window.FontFace(
formatFontFaceName( fontFace.fontFamily ),
dataSource,
{
style: fontFace.fontStyle,
weight: String( fontFace.fontWeight ),
}
);
const newFont = new window.FontFace( fontFace.fontFamily, dataSource, {
style: fontFace.fontStyle,
weight: String( fontFace.fontWeight ),
} );

const loadedFace = await newFont.load();

Expand Down Expand Up @@ -155,7 +150,7 @@ export function unloadFontFaceInBrowser(
const unloadFontFace = ( fonts: FontFaceSet ) => {
fonts.forEach( ( f ) => {
if (
f.family === formatFontFaceName( fontFace?.fontFamily ) &&
f.family === fontFace?.fontFamily &&
f.weight === fontFace?.fontWeight &&
f.style === fontFace?.fontStyle
) {
Expand Down Expand Up @@ -368,3 +363,37 @@ export function checkFontFaceInstalled(
} )
);
}

export function normalizeCSSFontFaceFontFamily( fontName: string ): string {
return `"${ fontName
.trim()

/*
* CSS Unicode escaping for problematic characters.
* https://www.w3.org/TR/css-syntax-3/#escaping
*
* These characters are not required by CSS but may be problematic in WordPress:
*
* - Normalize and replace newlines. https://www.w3.org/TR/css-syntax-3/#input-preprocessing
* - "<", ">", and "&" are replaced to prevent issues with KSES and other sanitization that
* is confused by HTML-like text.
* is confused by HTML-like text.
* - `,`, `"` and `'` are replaced to prevent issues where font families may be processed later.
*
* Note that the Unicode escape sequences are used rather than backslash-escaping so the
* problematic characters are removed completely.
*/
// Escape existing backslashes before any other processing
.replaceAll( '\\', '\\5C ' )
// Carriage return + line feed must be the first newline replacement.
.replaceAll( '\r\n', '\\A ' )
.replaceAll( '\r', '\\A ' )
.replaceAll( '\f', '\\A ' )
.replaceAll( '\n', '\\A ' )
.replaceAll( ',', '\\2C ' )
.replaceAll( '"', '\\22 ' )
.replaceAll( "'", '\\27 ' )
.replaceAll( '<', '\\3C ' )
.replaceAll( '>', '\\3E ' )
.replaceAll( '&', '\\26 ' ) }"`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,31 @@
* WordPress dependencies
*/
import { privateApis as componentsPrivateApis } from '@wordpress/components';
import type { FontFamily, FontFace } from '@wordpress/core-data';
import type { FontFamily } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import type { FontFaceMetadata } from '../upload-fonts';

const { kebabCase } = unlock( componentsPrivateApis );

export default function makeFamiliesFromFaces(
fontFaces: FontFace[]
fontFaces: FontFaceMetadata[]
): FontFamily[] {
const fontFamiliesObject = fontFaces.reduce(
( acc: Record< string, FontFamily >, item: FontFace ) => {
if ( ! acc[ item.fontFamily ] ) {
acc[ item.fontFamily ] = {
name: item.fontFamily,
fontFamily: item.fontFamily,
slug: kebabCase( item.fontFamily.toLowerCase() ),
fontFace: [],
};
}
// @ts-expect-error
acc[ item.fontFamily ].fontFace.push( item );
return acc;
},
{}
);
const fontFamiliesObject = new Map< string, FontFamily >();
for ( const item of fontFaces ) {
if ( fontFamiliesObject.has( item.fontFamily ) ) {
fontFamiliesObject.get( item.fontFamily )!.fontFace!.push( item );
}

fontFamiliesObject.set( item.fontFamily, {
name: item.name,
fontFamily: item.fontFamily,
slug: kebabCase( item.fontFamily.toLowerCase() ),
fontFace: [],
} );
}
return Object.values( fontFamiliesObject ) as FontFamily[];
}
38 changes: 0 additions & 38 deletions packages/global-styles-ui/src/font-library/utils/preview-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,44 +82,6 @@ export function formatFontFamily( input: string ) {
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: string ) {
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;
}

export function getFamilyPreviewStyle(
family: FontFamily | FontFace
): CSSProperties {
Expand Down
Loading