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

Font manager #46332

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bc5549c
font manager: first UI prototype
matiasbenedetto Nov 9, 2022
e4a932c
fixing theme fonts listing
matiasbenedetto Nov 9, 2022
a16bfa1
Download fonts in global styles controller
matiasbenedetto Dec 2, 2022
dcff780
Add and remove google fonts
matiasbenedetto Dec 2, 2022
a8dbdf4
Js lint
matiasbenedetto Dec 2, 2022
ec36991
removing reference to a testing file
matiasbenedetto Dec 2, 2022
fe8a957
commeting testing code
matiasbenedetto Dec 2, 2022
46d4c20
Merge branch 'trunk' into try/font-manager
matiasbenedetto Dec 8, 2022
5861e33
Global stettings: adding and removing fonts encoded in base64
matiasbenedetto Dec 22, 2022
18a654f
sort font families
matiasbenedetto Dec 22, 2022
4436bd5
sort font faces
matiasbenedetto Dec 22, 2022
7f6ed21
improving font families and font faces sorting and display
matiasbenedetto Dec 23, 2022
d02fb4c
merge trunk
matiasbenedetto Dec 23, 2022
f6d8bba
Fix navigation when all font faces are removed
matiasbenedetto Dec 27, 2022
9b5644c
change wording
matiasbenedetto Dec 28, 2022
0a13f52
available font faces: adding animation when removing or restoring fon…
matiasbenedetto Dec 28, 2022
d858af3
package-lock.json update
matiasbenedetto Dec 28, 2022
6c4c328
Merge branch 'trunk' into try/font-manager
matiasbenedetto Apr 26, 2023
0f71b6f
removing repeated import
matiasbenedetto Apr 26, 2023
47bf44d
fixing import path
matiasbenedetto Apr 26, 2023
2add287
fixing class name
matiasbenedetto Apr 26, 2023
07ee5d2
moving new fonts related code from 6.2 to 6.3 compat files
matiasbenedetto Apr 26, 2023
292eecd
removing not needed code
matiasbenedetto Apr 26, 2023
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
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ module.exports = {
'plugin:eslint-comments/recommended',
],
globals: {
File: 'readonly',
fetch: 'readonly',
FileReader: 'readonly',
wp: 'off',
},
settings: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,4 @@ public function get_theme_items( $request ) {

return $response;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,175 @@ protected function prepare_links( $id ) {

return $links;
}


protected function get_font_file_extension ( $mime ) {
$extensions = array(
'font/ttf' => 'ttf',
'font/woff' => 'woff',
'font/woff2' => 'woff2',
);
if ( isset( $extensions[ $mime] ) ) {
return $extensions[ $mime ];
}
throw new Exception('Mime type not allowed');
}

protected function base64_decode_file($data) {
if(preg_match('/^data\:([a-zA-Z]+\/[a-zA-Z]+);base64\,([a-zA-Z0-9\+\/]+\=*)$/', $data, $matches)) {
return [
'mime' => $matches[1],
'data' => base64_decode($matches[2]),
];
}
return false;
}

protected function delete_custom_fonts () {
$fonts_dir = $this->get_fonts_dir();
$files = glob( $fonts_dir['basedir'] . '/*' );
foreach ( $files as $file ) {
if ( is_file( $file ) ) {
unlink( $file );
}
}
}

protected function delete_font_asset ( $font_face ) {
$fonts_dir = $this->get_fonts_dir();
foreach ( $font_face['src'] as $url ) {
// TODO: make this work with relative urls too
$filename = basename( $url );
$filepath = $fonts_dir['basedir'] . $filename;
if ( file_exists( $filepath ) ) {
return unlink(
$filepath
);
}
}
return false;
}

protected function get_fonts_dir () {
$theme_data = wp_get_theme();
$theme_slug = $theme_data->get('TextDomain');
$upload_dir = wp_upload_dir();
$fonts_dir = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . $theme_slug . DIRECTORY_SEPARATOR;
$font_path = $upload_dir['baseurl']."/fonts/".$theme_slug."/".$file_name;
return array (
'basedir' => $fonts_dir,
'baseurl' => $font_path,
);
}

protected function write_font_face ( $font_face ) {
$font_asset = $this->base64_decode_file( $font_face['base64'] );

$file_extension = $this->get_font_file_extension( $font_asset[ 'mime' ] );
$file_name = $font_face['fontFamily'].'_'.$font_face['fontStyle'].'_'.$font_face['fontWeight'].'.'.$file_extension;

$fonts_dir = $this->get_fonts_dir();
wp_mkdir_p( $fonts_dir['basedir'] );
file_put_contents( $fonts_dir['basedir'] . $file_name, $font_asset['data'] );

$new_font_face = $font_face;
unset( $new_font_face['shouldBeDecoded'] );
unset( $new_font_face['base64'] );
$font_url = $fonts_dir['baseurl'] . $file_name;
$new_font_face['src'] = array ( $font_url );

return $new_font_face;
}

protected function prepare_font_families_for_database( $font_families ) {
$prepared_font_families = array();

foreach ( $font_families as $font_family ) {
if ( isset ( $font_family['fontFace'] ) ) {
$new_font_faces = array();
foreach ( $font_family['fontFace'] as $font_face ) {
$updated_font_face = $font_face;
if ( isset( $updated_font_face['shouldBeDecoded'] ) ) {
$updated_font_face = $this->write_font_face( $font_face );
}
if ( !isset ( $font_face['shouldBeRemoved'] ) ) {
$new_font_faces[] = $updated_font_face;
} else {
$this->delete_font_asset( $font_face );
}
}

$font_family['fontFace'] = $new_font_faces;
}
if ( ! isset ( $font_family[ 'shouldBeRemoved' ] ) ) {
$prepared_font_families[] = $font_family;
}

}

return $prepared_font_families;
}

/**
* Prepares a single global styles config for update.
*
* @since 5.9.0
* @since 6.2.0 Added validation of styles.css property.
*
* @param WP_REST_Request $request Request object.
* @return stdClass Changes to pass to wp_update_post.
*/
protected function prepare_item_for_database( $request ) {
$changes = new stdClass();
$changes->ID = $request['id'];
$post = get_post( $request['id'] );
$existing_config = array();
if ( $post ) {
$existing_config = json_decode( $post->post_content, true );
$json_decoding_error = json_last_error();
if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) ||
! $existing_config['isGlobalStylesUserThemeJSON'] ) {
$existing_config = array();
}
}
if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
$config = array();
if ( isset( $request['styles'] ) ) {
$config['styles'] = $request['styles'];
if ( isset( $request['styles']['css'] ) ) {
$validate_custom_css = $this->validate_custom_css( $request['styles']['css'] );
if ( is_wp_error( $validate_custom_css ) ) {
return $validate_custom_css;
}
}
} elseif ( isset( $existing_config['styles'] ) ) {
$config['styles'] = $existing_config['styles'];
}
if ( isset( $request['settings'] ) ) {
$config['settings'] = $request['settings'];
} elseif ( isset( $existing_config['settings'] ) ) {
$config['settings'] = $existing_config['settings'];
}

if ( isset ( $config['settings']['typography']['fontFamilies']['custom'] ) ) {
$new_fonts = $this->prepare_font_families_for_database( $config['settings']['typography']['fontFamilies']['custom'] );
$config['settings']['typography']['fontFamilies']['custom'] = $new_fonts;
} else {
$this->delete_custom_fonts();
}

$config['isGlobalStylesUserThemeJSON'] = true;
$config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA;
$changes->post_content = wp_json_encode( $config );
}
// Post title.
if ( isset( $request['title'] ) ) {
if ( is_string( $request['title'] ) ) {
$changes->post_title = $request['title'];
} elseif ( ! empty( $request['title']['raw'] ) ) {
$changes->post_title = $request['title']['raw'];
}
}
return $changes;
}
}
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/**
* Internal dependencies
*/
import { _x } from '@wordpress/i18n';
import { getComputedFluidTypographyValue } from '../font-sizes/fluid-utils';

/**
Expand Down Expand Up @@ -85,3 +86,20 @@ export function getTypographyFontSizeValue( preset, typographySettings ) {

return defaultSize;
}

export const FONT_STYLES = {
normal: _x( 'Regular', 'font style' ),
italic: _x( 'Italic', 'font style' ),
};

export const FONT_WEIGHTS = {
100: _x( 'Thin', 'font weight' ),
200: _x( 'Extra Light', 'font weight' ),
300: _x( 'Light', 'font weight' ),
400: _x( 'Regular', 'font weight' ),
500: _x( 'Medium', 'font weight' ),
600: _x( 'Semi Bold', 'font weight' ),
700: _x( 'Bold', 'font weight' ),
800: _x( 'Extra Bold', 'font weight' ),
900: _x( 'Black', 'font weight' ),
};
1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@react-spring/web": "^9.4.5",
"@wordpress/a11y": "file:../a11y",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/block-editor": "file:../block-editor",
Expand Down
106 changes: 106 additions & 0 deletions packages/edit-site/src/components/global-styles/add-font-face-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Icon, plusCircle, check } from '@wordpress/icons';
import { Tooltip, Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import FontFaceItem from './font-face-item';
import { useFontFamilies } from './hooks';

function readFileAsBase64( file ) {
return new Promise( ( resolve, reject ) => {
const reader = new FileReader();
reader.onload = () => {
resolve( reader.result );
};
reader.onerror = reject;
reader.readAsDataURL( file );
} );
}

async function downloadAndEncode( url ) {
try {
// Send an HTTP GET request to the URL
const response = await fetch( url );
// Check the status code of the response
if ( ! response.ok ) {
throw new Error(
`HTTP error: ${ response.status } ${ response.statusText }`
);
}
// Get the response as a blob
const blob = await response.blob();
const metadata = { type: blob.type };
const file = new File( [ blob ], 'font-file', metadata );
const base64 = await readFileAsBase64( file );
return base64;
} catch ( error ) {
return null;
}
}

function AddFontFace( { fontFace, isExistingFace } ) {
const { handleAddFontFace, handleRemoveFontFace } = useFontFamilies();

async function handleAdd( fontFamily, fontWeight, fontStyle, url ) {
const base64 = await downloadAndEncode( url );
if ( ! base64 ) {
// TODO: show error message
return;
}
const newFontFace = {
fontFamily,
fontWeight,
fontStyle,
base64,
};
handleAddFontFace( newFontFace );
}

return (
<FontFaceItem
key={ `${ fontFace.fontWeight }-${ fontFace.fontStyle }` }
fontFace={ fontFace }
actionTrigger={
! isExistingFace ? (
<Tooltip text={ __( 'Add font face' ) } delay={ 0 }>
<Button
style={ { padding: '0 8px' } }
onClick={ () =>
handleAdd(
fontFace.fontFamily,
fontFace.fontWeight,
fontFace.fontStyle,
fontFace.url
)
}
>
<Icon icon={ plusCircle } size={ 20 } />
</Button>
</Tooltip>
) : (
<Tooltip text={ __( 'Remove Font Face' ) } delay={ 0 }>
<Button
style={ { padding: '0 8px' } }
onClick={ () =>
handleRemoveFontFace(
fontFace.fontFamily,
fontFace.fontWeight,
fontFace.fontStyle
)
}
>
<Icon icon={ check } size={ 20 } />
</Button>
</Tooltip>
)
}
/>
);
}

export default AddFontFace;
Loading