Skip to content

Commit 5123e73

Browse files
Jiwoon-Kimclaude
andcommitted
Typography: Read the keywords a variable weight range may use
A `@font-face` weight range may name its ends with the absolute keywords the property defines, so "normal 900" is the range 400 to 900. Reading both ends as numbers dropped such a face: `Number( 'normal' )` is `NaN`, so the hundreds loop produced nothing and the font offered no weights at all. Resolve each end through the same keyword map the Font Library preview uses, `normal` as 400 and `bold` as 700, and only treat the face as variable once both ends resolve. `lighter` and `bolder` are relative to a parent and are not allowed on `@font-face`, so they are not mapped; a range naming them is left to the face's own formatting rather than inventing weights nobody declared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent db03962 commit 5123e73

3 files changed

Lines changed: 94 additions & 16 deletions

File tree

‎packages/block-editor/CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
### Bug Fixes
1818

19-
- Font appearance control: list only the weights inside a variable font's range, reading both ends of the range as whole numbers ([#83128](https://github.com/WordPress/gutenberg/pull/83128)).
19+
- `FontAppearanceControl`: List only the weights inside a variable font's range, reading both ends of the range as whole numbers or as the keywords `normal` and `bold` ([#83128](https://github.com/WordPress/gutenberg/pull/83128)).
2020
- `LinkControl`: Only label pages as the front page or blog home. Posts, terms and media are separate tables and can share an id, so an unscoped search could label a term "Front page" ([#83082](https://github.com/WordPress/gutenberg/pull/83082)).
2121
- `InnerBlocks`: Resolve a container's legacy layout markup (`inherit: true`, or a bare `contentSize` / `wideSize` with no `type`) to a constrained layout for its inner blocks, so they are offered the wide and full alignments. Previously only the container's styles honoured the legacy form, and the inner blocks resolved to the flow layout ([#82637](https://github.com/WordPress/gutenberg/pull/82637)).
2222
- Block Patterns, Block Visibility, and Block Lock: Preserve the intended colors of icons converted to strokes. ([#82540](https://github.com/WordPress/gutenberg/pull/82540), [#82754](https://github.com/WordPress/gutenberg/pull/82754))

‎packages/block-editor/src/utils/get-font-styles-and-weights.ts‎

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,34 @@ import type {
77
CombinedStyleAndWeightOption,
88
} from './types';
99

10+
/*
11+
* The absolute keywords a `@font-face` weight may use. `lighter` and `bolder` are
12+
* relative to the parent and are not allowed there, so they are not listed.
13+
*/
14+
const FONT_WEIGHT_KEYWORDS: Record< string, number | undefined > = {
15+
normal: 400,
16+
bold: 700,
17+
};
18+
19+
function isValidWeight( weight: number | undefined ): weight is number {
20+
return (
21+
weight !== undefined &&
22+
Number.isFinite( weight ) &&
23+
weight >= 1 &&
24+
weight <= 1000
25+
);
26+
}
27+
28+
/*
29+
* Read one end of a `@font-face` weight range as a number, or undefined when it is
30+
* neither a number nor a keyword the property accepts.
31+
*/
32+
function parseWeightValue( value: string ): number | undefined {
33+
const token = value.trim().toLowerCase();
34+
const weight = FONT_WEIGHT_KEYWORDS[ token ] ?? Number( token );
35+
return isValidWeight( weight ) ? weight : undefined;
36+
}
37+
1038
const FONT_STYLES = [
1139
{
1240
name: _x( 'Regular', 'font style' ),
@@ -83,22 +111,30 @@ export function getFontStylesAndWeights(
83111
'string' === typeof face.fontWeight &&
84112
/\s/.test( face.fontWeight.trim() )
85113
) {
86-
isVariableFont = true;
87-
88-
// Find the hundreds inside the range, e.g. 300 to 700 for "250 750".
114+
// Read both ends, which may be keywords: "normal 900" is 400 to 900.
89115
const [ startStr, endStr ] = face.fontWeight.trim().split( /\s+/ );
90-
const startValue = Math.ceil( parseInt( startStr, 10 ) / 100 );
91-
const endValue = Math.floor( parseInt( endStr, 10 ) / 100 );
92-
93-
// Create font weight options for available variable weights.
94-
for ( let i = startValue; i <= endValue; i++ ) {
95-
const fontWeightValue = `${ i.toString() }00`;
96-
if (
97-
! fontWeights.some(
98-
( weight ) => weight.value === fontWeightValue
99-
)
100-
) {
101-
fontWeights.push( formatFontWeight( fontWeightValue ) );
116+
const start = parseWeightValue( startStr );
117+
const end = parseWeightValue( endStr );
118+
119+
// A range this property cannot express is left to the face's own
120+
// formatting below rather than offering weights nobody declared.
121+
if ( start !== undefined && end !== undefined ) {
122+
isVariableFont = true;
123+
124+
// Find the hundreds inside the range, e.g. 300 to 700 for "250 750".
125+
const startValue = Math.ceil( start / 100 );
126+
const endValue = Math.floor( end / 100 );
127+
128+
// Create font weight options for available variable weights.
129+
for ( let i = startValue; i <= endValue; i++ ) {
130+
const fontWeightValue = `${ i.toString() }00`;
131+
if (
132+
! fontWeights.some(
133+
( weight ) => weight.value === fontWeightValue
134+
)
135+
) {
136+
fontWeights.push( formatFontWeight( fontWeightValue ) );
137+
}
102138
}
103139
}
104140
}

‎packages/block-editor/src/utils/test/get-font-styles-and-weights.js‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,48 @@ describe( 'getFontStylesAndWeights', () => {
706706
] );
707707
} );
708708

709+
it( 'reads the absolute keywords a range may use', () => {
710+
expect( weightsFor( 'normal 900' ) ).toEqual( [
711+
'400',
712+
'500',
713+
'600',
714+
'700',
715+
'800',
716+
'900',
717+
] );
718+
expect( weightsFor( '100 bold' ) ).toEqual( [
719+
'100',
720+
'200',
721+
'300',
722+
'400',
723+
'500',
724+
'600',
725+
'700',
726+
] );
727+
expect( weightsFor( 'NORMAL BOLD' ) ).toEqual( [
728+
'400',
729+
'500',
730+
'600',
731+
'700',
732+
] );
733+
} );
734+
735+
it( 'offers no weights for a range it cannot read, and does not call the face variable', () => {
736+
const result = getFontStylesAndWeights( [
737+
{
738+
fontFamily: 'Example Variable',
739+
fontStyle: 'normal',
740+
fontWeight: 'lighter bolder',
741+
src: 'https://example.org/example.woff2',
742+
},
743+
] );
744+
745+
expect( result.isVariableFont ).toBe( false );
746+
expect(
747+
result.fontWeights.map( ( weight ) => weight.value )
748+
).toEqual( [ 'lighter bolder' ] );
749+
} );
750+
709751
it( 'includes 1000 when the range reaches it', () => {
710752
expect( weightsFor( '100 1000' ) ).toEqual( [
711753
'100',

0 commit comments

Comments
 (0)