Skip to content

Commit

Permalink
Fluid typography: add font size constraints (#44993)
Browse files Browse the repository at this point in the history
* Initial commit:
- Adds default minimum font size limits so that text does not become smaller than 14px/0.875rem/0.875em. The consequence will be that all fluid font sizes will be no smaller than 14px/0.875rem/0.875em in any viewport width.
- Users may define font-sizes that are smaller than 14px/0.875rem/0.875em. In this case the user-defined font size becomes the minimum. For example if a user defines 9px for a block, then the minimum font size for that block will be 9px.

- Min font size should not be greater than max font size.
- Round to 3 decimal places (JS)

- Rounding computed min and max values

* Rounding parsed min and max values

* Updating JS rounding function so that values are rounded

* Removing type coercion

* Updating failing test

* Remove reference to preset in favour of "fontSize"

* Ensure that rem units are used when calculating min and max boundaries in em
This is to keep it consistent with the original formula and ensure the fluid argument in clamp is working off a size relative to :root

Fixing formatting and grammar
Rolling back renaming $preset to $font_size in preference to a follow up.

Do  not clamp value if there is no min size and the font size is less than 14px

* Oh Linter! You persistent crank.

* For font sizes of < 14px that have no defined minimum sizes, use the font size to set the floor of the clamp() value.

* Removing max < min check. It wasn't there before. It can be a follow up if required as it requires extra logic.

* Checking for a zero-based linear factor. If we find one, default to `1`

* Refer to correct JS var in JS file
  • Loading branch information
ramonjd committed Oct 19, 2022
1 parent 84269cf commit 3173e47
Show file tree
Hide file tree
Showing 6 changed files with 619 additions and 260 deletions.
76 changes: 62 additions & 14 deletions lib/block-supports/typography.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,17 @@ function gutenberg_get_typography_value_and_unit( $raw_value, $options = array()
$unit = $options['coerce_to'];
}

/*
* No calculation is required if swapping between em and rem yet,
* since we assume a root size value. Later we might like to differentiate between
* :root font size (rem) and parent element font size (em) relativity.
*/
if ( ( 'em' === $options['coerce_to'] || 'rem' === $options['coerce_to'] ) && ( 'em' === $unit || 'rem' === $unit ) ) {
$unit = $options['coerce_to'];
}

return array(
'value' => $value,
'value' => round( $value, 3 ),
'unit' => $unit,
);
}
Expand Down Expand Up @@ -357,27 +366,29 @@ function gutenberg_get_computed_fluid_typography_value( $args = array() ) {
$minimum_font_size_raw = isset( $args['minimum_font_size'] ) ? $args['minimum_font_size'] : null;
$scale_factor = isset( $args['scale_factor'] ) ? $args['scale_factor'] : null;

// Grab the minimum font size and normalize it in order to use the value for calculations.
// Normalizes the minimum font size in order to use the value for calculations.
$minimum_font_size = gutenberg_get_typography_value_and_unit( $minimum_font_size_raw );

// We get a 'preferred' unit to keep units consistent when calculating,
// otherwise the result will not be accurate.
/*
* We get a 'preferred' unit to keep units consistent when calculating,
* otherwise the result will not be accurate.
*/
$font_size_unit = isset( $minimum_font_size['unit'] ) ? $minimum_font_size['unit'] : 'rem';

// Grab the maximum font size and normalize it in order to use the value for calculations.
// Grabs the maximum font size and normalize it in order to use the value for calculations.
$maximum_font_size = gutenberg_get_typography_value_and_unit(
$maximum_font_size_raw,
array(
'coerce_to' => $font_size_unit,
)
);

// Protect against unsupported units.
// Checks for mandatory min and max sizes, and protects against unsupported units.
if ( ! $maximum_font_size || ! $minimum_font_size ) {
return null;
}

// Use rem for accessible fluid target font scaling.
// Uses rem for accessible fluid target font scaling.
$minimum_font_size_rem = gutenberg_get_typography_value_and_unit(
$minimum_font_size_raw,
array(
Expand All @@ -403,8 +414,9 @@ function gutenberg_get_computed_fluid_typography_value( $args = array() ) {
// Borrowed from https://websemantics.uk/tools/responsive-font-calculator/.
$view_port_width_offset = round( $minimum_viewport_width['value'] / 100, 3 ) . $font_size_unit;
$linear_factor = 100 * ( ( $maximum_font_size['value'] - $minimum_font_size['value'] ) / ( $maximum_viewport_width['value'] - $minimum_viewport_width['value'] ) );
$linear_factor = round( $linear_factor, 3 ) * $scale_factor;
$fluid_target_font_size = implode( '', $minimum_font_size_rem ) . " + ((1vw - $view_port_width_offset) * $linear_factor)";
$linear_factor_scaled = round( $linear_factor * $scale_factor, 3 );
$linear_factor_scaled = empty( $linear_factor_scaled ) ? 1 : $linear_factor_scaled;
$fluid_target_font_size = implode( '', $minimum_font_size_rem ) . " + ((1vw - $view_port_width_offset) * $linear_factor_scaled)";

return "clamp($minimum_font_size_raw, $fluid_target_font_size, $maximum_font_size_raw)";
}
Expand Down Expand Up @@ -437,7 +449,7 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty
return $preset['size'];
}

// Check if fluid font sizes are activated.
// Checks if fluid font sizes are activated.
$typography_settings = gutenberg_get_global_settings( array( 'typography' ) );
$should_use_fluid_typography = isset( $typography_settings['fluid'] ) && true === $typography_settings['fluid'] ? true : $should_use_fluid_typography;

Expand All @@ -451,6 +463,7 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty
$default_minimum_font_size_factor = 0.75;
$default_maximum_font_size_factor = 1.5;
$default_scale_factor = 1;
$default_minimum_font_size_limit = '14px';

// Font sizes.
$fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null;
Expand All @@ -472,13 +485,48 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty
return $preset['size'];
}

// If no fluid min or max font sizes are available, create some using min/max font size factors.
// If no fluid max font size is available, create one using max font size factor.
if ( ! $maximum_font_size_raw ) {
$maximum_font_size_raw = round( $preferred_size['value'] * $default_maximum_font_size_factor, 3 ) . $preferred_size['unit'];
}

// If no fluid min font size is available, create one using min font size factor.
if ( ! $minimum_font_size_raw ) {
$minimum_font_size_raw = ( $preferred_size['value'] * $default_minimum_font_size_factor ) . $preferred_size['unit'];
$minimum_font_size_raw = round( $preferred_size['value'] * $default_minimum_font_size_factor, 3 ) . $preferred_size['unit'];
}

if ( ! $maximum_font_size_raw ) {
$maximum_font_size_raw = ( $preferred_size['value'] * $default_maximum_font_size_factor ) . $preferred_size['unit'];
// Parses the minimum font size limit, so we can perform checks using it.
$minimum_font_size_limit = gutenberg_get_typography_value_and_unit(
$default_minimum_font_size_limit,
array(
'coerce_to' => $preferred_size['unit'],
)
);

if ( ! empty( $minimum_font_size_limit ) ) {
/*
* If a minimum size was not passed to this function
* and the user-defined font size is lower than $minimum_font_size_limit,
* then uses the user-defined font size as the minimum font-size.
*/
if ( ! isset( $fluid_font_size_settings['min'] ) && $preferred_size['value'] < $minimum_font_size_limit['value'] ) {
$minimum_font_size_raw = implode( '', $preferred_size );
} else {
$minimum_font_size_parsed = gutenberg_get_typography_value_and_unit(
$minimum_font_size_raw,
array(
'coerce_to' => $preferred_size['unit'],
)
);

/*
* If the passed or calculated minimum font size is lower than $minimum_font_size_limit
* use $minimum_font_size_limit instead.
*/
if ( ! empty( $minimum_font_size_parsed ) && $minimum_font_size_parsed['value'] < $minimum_font_size_limit['value'] ) {
$minimum_font_size_raw = implode( '', $minimum_font_size_limit );
}
}
}

$fluid_font_size_value = gutenberg_get_computed_fluid_typography_value(
Expand Down
125 changes: 100 additions & 25 deletions packages/block-editor/src/components/font-sizes/fluid-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const DEFAULT_MINIMUM_VIEWPORT_WIDTH = '768px';
const DEFAULT_SCALE_FACTOR = 1;
const DEFAULT_MINIMUM_FONT_SIZE_FACTOR = 0.75;
const DEFAULT_MAXIMUM_FONT_SIZE_FACTOR = 1.5;
const DEFAULT_MINIMUM_FONT_SIZE_LIMIT = '14px';

/**
* Computes a fluid font-size value that uses clamp(). A minimum and maxinmum
Expand Down Expand Up @@ -53,11 +54,20 @@ export function getComputedFluidTypographyValue( {
scaleFactor = DEFAULT_SCALE_FACTOR,
minimumFontSizeFactor = DEFAULT_MINIMUM_FONT_SIZE_FACTOR,
maximumFontSizeFactor = DEFAULT_MAXIMUM_FONT_SIZE_FACTOR,
minimumFontSizeLimit = DEFAULT_MINIMUM_FONT_SIZE_LIMIT,
} ) {
// Calculate missing minimumFontSize and maximumFontSize from
// defaultFontSize if provided.
if ( fontSize && ( ! minimumFontSize || ! maximumFontSize ) ) {
// Parse default font size.
/*
* Caches minimumFontSize in minimumFontSizeValue
* so we can check if minimumFontSize exists later.
*/
let minimumFontSizeValue = minimumFontSize;

/*
* Calculates missing minimumFontSize and maximumFontSize from
* defaultFontSize if provided.
*/
if ( fontSize ) {
// Parses default font size.
const fontSizeParsed = getTypographyValueAndUnit( fontSize );

// Protect against invalid units.
Expand All @@ -66,46 +76,95 @@ export function getComputedFluidTypographyValue( {
}

// If no minimumFontSize is provided, derive using min scale factor.
if ( ! minimumFontSize ) {
minimumFontSize =
fontSizeParsed.value * minimumFontSizeFactor +
fontSizeParsed.unit;
if ( ! minimumFontSizeValue ) {
minimumFontSizeValue =
roundToPrecision(
fontSizeParsed.value * minimumFontSizeFactor,
3
) + fontSizeParsed.unit;
}

// Parses the minimum font size limit, so we can perform checks using it.
const minimumFontSizeLimitParsed = getTypographyValueAndUnit(
minimumFontSizeLimit,
{
coerceTo: fontSizeParsed.unit,
}
);

if ( !! minimumFontSizeLimitParsed?.value ) {
/*
* If a minimum size was not passed to this function
* and the user-defined font size is lower than `minimumFontSizeLimit`,
* then uses the user-defined font size as the minimum font-size.
*/
if (
! minimumFontSize &&
fontSizeParsed?.value < minimumFontSizeLimitParsed?.value
) {
minimumFontSizeValue = `${ fontSizeParsed.value }${ fontSizeParsed.unit }`;
} else {
const minimumFontSizeParsed = getTypographyValueAndUnit(
minimumFontSizeValue,
{
coerceTo: fontSizeParsed.unit,
}
);

/*
* Otherwise, if the passed or calculated minimum font size is lower than `minimumFontSizeLimit`
* use `minimumFontSizeLimit` instead.
*/
if (
!! minimumFontSizeParsed?.value &&
minimumFontSizeParsed.value <
minimumFontSizeLimitParsed.value
) {
minimumFontSizeValue = `${ minimumFontSizeLimitParsed.value }${ minimumFontSizeLimitParsed.unit }`;
}
}
}

// If no maximumFontSize is provided, derive using max scale factor.
if ( ! maximumFontSize ) {
maximumFontSize =
fontSizeParsed.value * maximumFontSizeFactor +
fontSizeParsed.unit;
roundToPrecision(
fontSizeParsed.value * maximumFontSizeFactor,
3
) + fontSizeParsed.unit;
}
}

// Return early if one of the provided inputs is not provided.
if ( ! minimumFontSize || ! maximumFontSize ) {
if ( ! minimumFontSizeValue || ! maximumFontSize ) {
return null;
}

// Grab the minimum font size and normalize it in order to use the value for calculations.
const minimumFontSizeParsed = getTypographyValueAndUnit( minimumFontSize );
const minimumFontSizeParsed =
getTypographyValueAndUnit( minimumFontSizeValue );

// We get a 'preferred' unit to keep units consistent when calculating,
// otherwise the result will not be accurate.
const fontSizeUnit = minimumFontSizeParsed?.unit || 'rem';

// Grab the maximum font size and normalize it in order to use the value for calculations.
// Grabs the maximum font size and normalize it in order to use the value for calculations.
const maximumFontSizeParsed = getTypographyValueAndUnit( maximumFontSize, {
coerceTo: fontSizeUnit,
} );

// Protect against unsupported units.
// Checks for mandatory min and max sizes, and protects against unsupported units.
if ( ! minimumFontSizeParsed || ! maximumFontSizeParsed ) {
return null;
}

// Use rem for accessible fluid target font scaling.
const minimumFontSizeRem = getTypographyValueAndUnit( minimumFontSize, {
coerceTo: 'rem',
} );
// Uses rem for accessible fluid target font scaling.
const minimumFontSizeRem = getTypographyValueAndUnit(
minimumFontSizeValue,
{
coerceTo: 'rem',
}
);

// Viewport widths defined for fluid typography. Normalize units
const maximumViewPortWidthParsed = getTypographyValueAndUnit(
Expand Down Expand Up @@ -133,17 +192,20 @@ export function getComputedFluidTypographyValue( {
3
);

const viewPortWidthOffset = minViewPortWidthOffsetValue + fontSizeUnit;
let linearFactor =
const viewPortWidthOffset =
roundToPrecision( minViewPortWidthOffsetValue, 3 ) + fontSizeUnit;
const linearFactor =
100 *
( ( maximumFontSizeParsed.value - minimumFontSizeParsed.value ) /
( maximumViewPortWidthParsed.value -
minumumViewPortWidthParsed.value ) );
linearFactor = roundToPrecision( linearFactor, 3 ) || 1;
const linearFactorScaled = linearFactor * scaleFactor;
const linearFactorScaled = roundToPrecision(
( linearFactor || 1 ) * scaleFactor,
3
);
const fluidTargetFontSize = `${ minimumFontSizeRem.value }${ minimumFontSizeRem.unit } + ((1vw - ${ viewPortWidthOffset }) * ${ linearFactorScaled })`;

return `clamp(${ minimumFontSize }, ${ fluidTargetFontSize }, ${ maximumFontSize })`;
return `clamp(${ minimumFontSizeValue }, ${ fluidTargetFontSize }, ${ maximumFontSize })`;
}

/**
Expand Down Expand Up @@ -199,8 +261,20 @@ export function getTypographyValueAndUnit( rawValue, options = {} ) {
unit = coerceTo;
}

/*
* No calculation is required if swapping between em and rem yet,
* since we assume a root size value. Later we might like to differentiate between
* :root font size (rem) and parent element font size (em) relativity.
*/
if (
( 'em' === coerceTo || 'rem' === coerceTo ) &&
( 'em' === unit || 'rem' === unit )
) {
unit = coerceTo;
}

return {
value: returnValue,
value: roundToPrecision( returnValue, 3 ),
unit,
};
}
Expand All @@ -215,7 +289,8 @@ export function getTypographyValueAndUnit( rawValue, options = {} ) {
* @return {number|undefined} Value rounded to standard precision.
*/
export function roundToPrecision( value, digits = 3 ) {
const base = Math.pow( 10, digits );
return Number.isFinite( value )
? parseFloat( value.toFixed( digits ) )
? parseFloat( Math.round( value * base ) / base )
: undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe( 'getComputedFluidTypographyValue()', () => {
fontSize: '30px',
} );
expect( fluidTypographyValues ).toBe(
'clamp(22.5px, 1.40625rem + ((1vw - 7.68px) * 2.704), 45px)'
'clamp(22.5px, 1.406rem + ((1vw - 7.68px) * 2.704), 45px)'
);
} );

Expand All @@ -42,7 +42,7 @@ describe( 'getComputedFluidTypographyValue()', () => {
fontSize: '30px',
} );
expect( fluidTypographyValues ).toBe(
'clamp(22.5px, 1.40625rem + ((1vw - 7.68px) * 2.704), 45px)'
'clamp(22.5px, 1.406rem + ((1vw - 7.68px) * 2.704), 45px)'
);
} );

Expand All @@ -53,7 +53,7 @@ describe( 'getComputedFluidTypographyValue()', () => {
maximumViewPortWidth: '1000px',
} );
expect( fluidTypographyValues ).toBe(
'clamp(22.5px, 1.40625rem + ((1vw - 5px) * 4.5), 45px)'
'clamp(22.5px, 1.406rem + ((1vw - 5px) * 4.5), 45px)'
);
} );

Expand All @@ -63,7 +63,7 @@ describe( 'getComputedFluidTypographyValue()', () => {
scaleFactor: '2',
} );
expect( fluidTypographyValues ).toBe(
'clamp(22.5px, 1.40625rem + ((1vw - 7.68px) * 5.408), 45px)'
'clamp(22.5px, 1.406rem + ((1vw - 7.68px) * 5.409), 45px)'
);
} );

Expand All @@ -74,7 +74,7 @@ describe( 'getComputedFluidTypographyValue()', () => {
maximumFontSizeFactor: '2',
} );
expect( fluidTypographyValues ).toBe(
'clamp(15px, 0.9375rem + ((1vw - 7.68px) * 5.409), 60px)'
'clamp(15px, 0.938rem + ((1vw - 7.68px) * 5.409), 60px)'
);
} );

Expand Down
Loading

0 comments on commit 3173e47

Please sign in to comment.