Skip to content

Commit 139e471

Browse files
Jiwoon-KimJiwoon-Kimjuanfrat-hamano
authored
Typography: List only the weights inside a variable font's range (#83128)
* Typography: List only the weights inside a variable font's range getFontStylesAndWeights() read each end of a "start end" fontWeight range from its first digit, so "250 750" offered 200, which the font cannot draw, and "50 900" started at 500 and left out 100 to 400. Parse the whole numbers and list the hundreds from the first at or above the start to the last at or below the end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Typography: Add a changelog entry for the weight range fix Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 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> --------- Co-authored-by: Jiwoon-Kim <kimjiwoon@git.wordpress.org> Co-authored-by: juanfra <juanfra@git.wordpress.org> Co-authored-by: t-hamano <wildworks@git.wordpress.org>
1 parent d2ef2d4 commit 139e471

3 files changed

Lines changed: 160 additions & 17 deletions

File tree

‎packages/block-editor/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
- `BlockAlignmentControl`: Hide the control again when none of a block's alignments are available. Paragraph, Heading, List and other blocks that support only wide and full were showing a menu of unavailable options that could not change anything ([#83265](https://github.com/WordPress/gutenberg/pull/83265)).
3838
- Inline images in rich text are selected by clicking them again. A block captures a drag that starts on its own image through the `dragstart` target instead of the `pointer-events: none` rule that also kept the image from any click ([#83370](https://github.com/WordPress/gutenberg/pull/83370)).
3939
- `RichText`: The edit UI of a format follows the active object when the selection moves from one object straight to another, for example from one inline image to the next, so the popover shows the attributes of that object at its position ([#83370](https://github.com/WordPress/gutenberg/pull/83370)).
40+
- `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)).
4041
- `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)).
4142
- `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)).
4243
- 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: 52 additions & 17 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,23 +111,30 @@ export function getFontStylesAndWeights(
83111
'string' === typeof face.fontWeight &&
84112
/\s/.test( face.fontWeight.trim() )
85113
) {
86-
isVariableFont = true;
87-
88-
// Find font weight start and end values.
89-
const [ startStr, endStr ] = face.fontWeight.split( ' ' );
90-
const startValue = parseInt( startStr.slice( 0, 1 ) );
91-
const endValue =
92-
endStr === '1000' ? 10 : parseInt( endStr.slice( 0, 1 ) );
93-
94-
// Create font weight options for available variable weights.
95-
for ( let i = startValue; i <= endValue; i++ ) {
96-
const fontWeightValue = `${ i.toString() }00`;
97-
if (
98-
! fontWeights.some(
99-
( weight ) => weight.value === fontWeightValue
100-
)
101-
) {
102-
fontWeights.push( formatFontWeight( fontWeightValue ) );
114+
// Read both ends, which may be keywords: "normal 900" is 400 to 900.
115+
const [ startStr, endStr ] = face.fontWeight.trim().split( /\s+/ );
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+
}
103138
}
104139
}
105140
}

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

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,4 +656,111 @@ describe( 'getFontStylesAndWeights', () => {
656656
isVariableFont: false,
657657
} );
658658
} );
659+
660+
describe( 'variable font weight ranges', () => {
661+
const weightsFor = ( fontWeight ) =>
662+
getFontStylesAndWeights( [
663+
{
664+
fontFamily: 'Example Variable',
665+
fontStyle: 'normal',
666+
fontWeight,
667+
src: 'https://example.org/example.woff2',
668+
},
669+
] ).fontWeights.map( ( weight ) => weight.value );
670+
671+
it( 'lists the hundreds inside a range that starts between hundreds', () => {
672+
expect( weightsFor( '250 750' ) ).toEqual( [
673+
'300',
674+
'400',
675+
'500',
676+
'600',
677+
'700',
678+
] );
679+
} );
680+
681+
it( 'lists the hundreds inside a range that starts below 100', () => {
682+
expect( weightsFor( '50 900' ) ).toEqual( [
683+
'100',
684+
'200',
685+
'300',
686+
'400',
687+
'500',
688+
'600',
689+
'700',
690+
'800',
691+
'900',
692+
] );
693+
} );
694+
695+
it( 'lists the hundreds up to a range that ends between hundreds', () => {
696+
expect( weightsFor( '100 950' ) ).toEqual( [
697+
'100',
698+
'200',
699+
'300',
700+
'400',
701+
'500',
702+
'600',
703+
'700',
704+
'800',
705+
'900',
706+
] );
707+
} );
708+
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+
751+
it( 'includes 1000 when the range reaches it', () => {
752+
expect( weightsFor( '100 1000' ) ).toEqual( [
753+
'100',
754+
'200',
755+
'300',
756+
'400',
757+
'500',
758+
'600',
759+
'700',
760+
'800',
761+
'900',
762+
'1000',
763+
] );
764+
} );
765+
} );
659766
} );

0 commit comments

Comments
 (0)