From cb0b5296f39a2574df0452fc41f952aeb66c6f9e Mon Sep 17 00:00:00 2001 From: Jiwoon-Kim <71701140+Jiwoon-Kim@users.noreply.github.com> Date: Fri, 18 Sep 2026 20:40:38 +0900 Subject: [PATCH 1/3] 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 --- .../src/utils/get-font-styles-and-weights.ts | 9 ++- .../utils/test/get-font-styles-and-weights.js | 65 +++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/utils/get-font-styles-and-weights.ts b/packages/block-editor/src/utils/get-font-styles-and-weights.ts index 1dd2d88bbde1f3..472f37eaa7fbba 100644 --- a/packages/block-editor/src/utils/get-font-styles-and-weights.ts +++ b/packages/block-editor/src/utils/get-font-styles-and-weights.ts @@ -85,11 +85,10 @@ export function getFontStylesAndWeights( ) { isVariableFont = true; - // Find font weight start and end values. - const [ startStr, endStr ] = face.fontWeight.split( ' ' ); - const startValue = parseInt( startStr.slice( 0, 1 ) ); - const endValue = - endStr === '1000' ? 10 : parseInt( endStr.slice( 0, 1 ) ); + // Find the hundreds inside the range, e.g. 300 to 700 for "250 750". + const [ startStr, endStr ] = face.fontWeight.trim().split( /\s+/ ); + const startValue = Math.ceil( parseInt( startStr, 10 ) / 100 ); + const endValue = Math.floor( parseInt( endStr, 10 ) / 100 ); // Create font weight options for available variable weights. for ( let i = startValue; i <= endValue; i++ ) { diff --git a/packages/block-editor/src/utils/test/get-font-styles-and-weights.js b/packages/block-editor/src/utils/test/get-font-styles-and-weights.js index 152d5ced9dae06..c5f33af8e1a156 100644 --- a/packages/block-editor/src/utils/test/get-font-styles-and-weights.js +++ b/packages/block-editor/src/utils/test/get-font-styles-and-weights.js @@ -656,4 +656,69 @@ describe( 'getFontStylesAndWeights', () => { isVariableFont: false, } ); } ); + + describe( 'variable font weight ranges', () => { + const weightsFor = ( fontWeight ) => + getFontStylesAndWeights( [ + { + fontFamily: 'Example Variable', + fontStyle: 'normal', + fontWeight, + src: 'https://example.org/example.woff2', + }, + ] ).fontWeights.map( ( weight ) => weight.value ); + + it( 'lists the hundreds inside a range that starts between hundreds', () => { + expect( weightsFor( '250 750' ) ).toEqual( [ + '300', + '400', + '500', + '600', + '700', + ] ); + } ); + + it( 'lists the hundreds inside a range that starts below 100', () => { + expect( weightsFor( '50 900' ) ).toEqual( [ + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ] ); + } ); + + it( 'lists the hundreds up to a range that ends between hundreds', () => { + expect( weightsFor( '100 950' ) ).toEqual( [ + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + ] ); + } ); + + it( 'includes 1000 when the range reaches it', () => { + expect( weightsFor( '100 1000' ) ).toEqual( [ + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + '1000', + ] ); + } ); + } ); } ); From db03962623339e91b4f5eb9536677497477d72b5 Mon Sep 17 00:00:00 2001 From: Jiwoon-Kim <71701140+Jiwoon-Kim@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:07:50 +0900 Subject: [PATCH 2/3] Typography: Add a changelog entry for the weight range fix Co-Authored-By: Claude Opus 5 --- packages/block-editor/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 3e183d48342e12..8fb68f9af491e0 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -16,6 +16,7 @@ ### Bug Fixes +- 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)). - `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)). - `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)). - 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)) From 5123e7365db91f6eba38ae6c78d608219ef2f9be Mon Sep 17 00:00:00 2001 From: Jiwoon-Kim <71701140+Jiwoon-Kim@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:48:17 +0900 Subject: [PATCH 3/3] 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 --- packages/block-editor/CHANGELOG.md | 2 +- .../src/utils/get-font-styles-and-weights.ts | 66 ++++++++++++++----- .../utils/test/get-font-styles-and-weights.js | 42 ++++++++++++ 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 8fb68f9af491e0..22564c2486eb8b 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -16,7 +16,7 @@ ### Bug Fixes -- 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)). +- `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)). - `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)). - `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)). - 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)) diff --git a/packages/block-editor/src/utils/get-font-styles-and-weights.ts b/packages/block-editor/src/utils/get-font-styles-and-weights.ts index 472f37eaa7fbba..e5ad3266ba22fe 100644 --- a/packages/block-editor/src/utils/get-font-styles-and-weights.ts +++ b/packages/block-editor/src/utils/get-font-styles-and-weights.ts @@ -7,6 +7,34 @@ import type { CombinedStyleAndWeightOption, } from './types'; +/* + * The absolute keywords a `@font-face` weight may use. `lighter` and `bolder` are + * relative to the parent and are not allowed there, so they are not listed. + */ +const FONT_WEIGHT_KEYWORDS: Record< string, number | undefined > = { + normal: 400, + bold: 700, +}; + +function isValidWeight( weight: number | undefined ): weight is number { + return ( + weight !== undefined && + Number.isFinite( weight ) && + weight >= 1 && + weight <= 1000 + ); +} + +/* + * Read one end of a `@font-face` weight range as a number, or undefined when it is + * neither a number nor a keyword the property accepts. + */ +function parseWeightValue( value: string ): number | undefined { + const token = value.trim().toLowerCase(); + const weight = FONT_WEIGHT_KEYWORDS[ token ] ?? Number( token ); + return isValidWeight( weight ) ? weight : undefined; +} + const FONT_STYLES = [ { name: _x( 'Regular', 'font style' ), @@ -83,22 +111,30 @@ export function getFontStylesAndWeights( 'string' === typeof face.fontWeight && /\s/.test( face.fontWeight.trim() ) ) { - isVariableFont = true; - - // Find the hundreds inside the range, e.g. 300 to 700 for "250 750". + // Read both ends, which may be keywords: "normal 900" is 400 to 900. const [ startStr, endStr ] = face.fontWeight.trim().split( /\s+/ ); - const startValue = Math.ceil( parseInt( startStr, 10 ) / 100 ); - const endValue = Math.floor( parseInt( endStr, 10 ) / 100 ); - - // Create font weight options for available variable weights. - for ( let i = startValue; i <= endValue; i++ ) { - const fontWeightValue = `${ i.toString() }00`; - if ( - ! fontWeights.some( - ( weight ) => weight.value === fontWeightValue - ) - ) { - fontWeights.push( formatFontWeight( fontWeightValue ) ); + const start = parseWeightValue( startStr ); + const end = parseWeightValue( endStr ); + + // A range this property cannot express is left to the face's own + // formatting below rather than offering weights nobody declared. + if ( start !== undefined && end !== undefined ) { + isVariableFont = true; + + // Find the hundreds inside the range, e.g. 300 to 700 for "250 750". + const startValue = Math.ceil( start / 100 ); + const endValue = Math.floor( end / 100 ); + + // Create font weight options for available variable weights. + for ( let i = startValue; i <= endValue; i++ ) { + const fontWeightValue = `${ i.toString() }00`; + if ( + ! fontWeights.some( + ( weight ) => weight.value === fontWeightValue + ) + ) { + fontWeights.push( formatFontWeight( fontWeightValue ) ); + } } } } diff --git a/packages/block-editor/src/utils/test/get-font-styles-and-weights.js b/packages/block-editor/src/utils/test/get-font-styles-and-weights.js index c5f33af8e1a156..7902c967528857 100644 --- a/packages/block-editor/src/utils/test/get-font-styles-and-weights.js +++ b/packages/block-editor/src/utils/test/get-font-styles-and-weights.js @@ -706,6 +706,48 @@ describe( 'getFontStylesAndWeights', () => { ] ); } ); + it( 'reads the absolute keywords a range may use', () => { + expect( weightsFor( 'normal 900' ) ).toEqual( [ + '400', + '500', + '600', + '700', + '800', + '900', + ] ); + expect( weightsFor( '100 bold' ) ).toEqual( [ + '100', + '200', + '300', + '400', + '500', + '600', + '700', + ] ); + expect( weightsFor( 'NORMAL BOLD' ) ).toEqual( [ + '400', + '500', + '600', + '700', + ] ); + } ); + + it( 'offers no weights for a range it cannot read, and does not call the face variable', () => { + const result = getFontStylesAndWeights( [ + { + fontFamily: 'Example Variable', + fontStyle: 'normal', + fontWeight: 'lighter bolder', + src: 'https://example.org/example.woff2', + }, + ] ); + + expect( result.isVariableFont ).toBe( false ); + expect( + result.fontWeights.map( ( weight ) => weight.value ) + ).toEqual( [ 'lighter bolder' ] ); + } ); + it( 'includes 1000 when the range reaches it', () => { expect( weightsFor( '100 1000' ) ).toEqual( [ '100',