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

Block supports: add fluid typography #39529

Merged
merged 24 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5901eaa
Initial commit
ramonjd Mar 17, 2022
7cb5b09
Enabling fluid type in theme.json
ramonjd Mar 17, 2022
6b96585
Alternative calculation for fluid type
ramonjd Mar 21, 2022
1182f26
Implementing new algorithm using max and mix viewport widths
ramonjd Mar 22, 2022
3998a1d
Refactoring the method to accept a `maxSize`
ramonjd Mar 23, 2022
82caf16
The linter of doom!
ramonjd Mar 23, 2022
3b7c42d
Extracting internal implementation.
ramonjd Mar 28, 2022
9efbd8b
Added missing doc comment for fluid prop in theme.json
ramonjd Apr 26, 2022
f2d1668
Remove dupe presets
ramonjd Apr 27, 2022
15403eb
Remove dupe settings
ramonjd Apr 27, 2022
912b80e
Creating new compat file for 6.1
ramonjd Apr 29, 2022
69168ba
Created fallback for min and max viewport widths
ramonjd Apr 29, 2022
fed9f2f
Checking for valid units
ramonjd Apr 29, 2022
dd25039
Looking in layout settings for viewport width fallbacks
ramonjd May 2, 2022
aa67638
Post-rebase file shuffling.
ramonjd May 16, 2022
9304829
Update CHANGELOG.md
ramonjd May 26, 2022
e74b07b
min(), max() and clamp() automatically parse mathematic expressions s…
ramonjd May 27, 2022
00c51d1
We're now supporting passing single values to the fluid type calculat…
ramonjd Jun 17, 2022
eea3aeb
Refactoring function signatures to pass options array instead of mult…
ramonjd Jun 24, 2022
15b5bee
Merge default args in gutenberg_get_typography_value_and_unit using w…
ramonjd Jun 24, 2022
62beef5
Fix merge conflicts with trunk.
ramonjd Jul 13, 2022
edeaca7
- add to settings > fluid would be a boolean true to enable the feature.
ramonjd Jul 14, 2022
ded379d
- update docs
ramonjd Jul 15, 2022
62fa6f8
reverse min/max viewport width
scruffian Jul 15, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,13 @@ Settings related to typography.
| customFontSize | boolean | true | |
| fontStyle | boolean | true | |
| fontWeight | boolean | true | |
| fluid | boolean | | |
| letterSpacing | boolean | true | |
| lineHeight | boolean | false | |
| textDecoration | boolean | true | |
| textTransform | boolean | true | |
| dropCap | boolean | true | |
| fontSizes | array | | name, size, slug |
| fontSizes | array | | fluid, name, size, slug |
| fontFamilies | array | | fontFace, fontFamily, name, slug |

---
Expand Down
194 changes: 194 additions & 0 deletions lib/block-supports/typography.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,200 @@ function gutenberg_typography_get_css_variable_inline_style( $attributes, $featu
return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug );
}

/**
* Internal method that checks a string for a unit and value and returns an array consisting of `'value'` and `'unit'`, e.g., [ '42', 'rem' ].
*
* @access private
*
* @param string $raw_value Raw size value from theme.json.
* @param array $options array(
* 'coerce_to' => (string) Coerce the value to rem or px. Default `'rem'`.
* 'root_size_value' => (number) Value of root font size for rem|em <-> px conversion. Default `16`.
* 'acceptable_units' => (array) An array of font size units. Default `[ 'rem', 'px', 'em' ]`;
* );.
* @return array An array consisting of `'value'` and `'unit'`, e.g., [ '42', 'rem' ]
*/
function gutenberg_get_typography_value_and_unit( $raw_value, $options = array() ) {
if ( empty( $raw_value ) ) {
return null;
}

$defaults = array(
'coerce_to' => '',
'root_size_value' => 16,
'acceptable_units' => array( 'rem', 'px', 'em' ),
);

$options = wp_parse_args( $options, $defaults );

$acceptable_units_group = implode( '|', $options['acceptable_units'] );
$pattern = '/^(\d*\.?\d+)(' . $acceptable_units_group . '){1,1}$/';

preg_match( $pattern, $raw_value, $matches );

// We need a number value and a px or rem unit.
if ( ! isset( $matches[1] ) || ! isset( $matches[2] ) ) {
return null;
}

$value = $matches[1];
$unit = $matches[2];

// Default browser font size. Later we could inject some JS to compute this `getComputedStyle( document.querySelector( "html" ) ).fontSize`.
if ( 'px' === $options['coerce_to'] && ( 'em' === $unit || 'rem' === $unit ) ) {
$value = $value * $options['root_size_value'];
$unit = $options['coerce_to'];
}

if ( 'px' === $unit && ( 'em' === $options['coerce_to'] || 'rem' === $options['coerce_to'] ) ) {
$value = $value / $options['root_size_value'];
$unit = $options['coerce_to'];
}

return array(
'value' => $value,
'unit' => $unit,
);
}

/**
* Internal implementation of clamp() based on available min/max viewport width, and min/max font sizes.
*
* @access private
*
* @param array $args array(
* 'maximum_viewport_width' => (string) Maximum size up to which type will have fluidity.
* 'minimum_viewport_width' => (string) Minimum viewport size from which type will have fluidity.
* 'maximum_font_size' => (string) Maximum font size for any clamp() calculation.
* 'minimum_font_size' => (string) Minimum font size for any clamp() calculation.
* 'scale_factor' => (number) A scale factor to determine how fast a font scales within boundaries.
* );.
* @return string|null A font-size value using clamp().
*/
function gutenberg_get_computed_fluid_typography_value( $args = array() ) {
$maximum_viewport_width_raw = isset( $args['maximum_viewport_width'] ) ? $args['maximum_viewport_width'] : null;
$minimum_viewport_width_raw = isset( $args['minimum_viewport_width'] ) ? $args['minimum_viewport_width'] : null;
$maximum_font_size_raw = isset( $args['maximum_font_size'] ) ? $args['maximum_font_size'] : null;
$minimum_font_size_raw = isset( $args['minimum_font_size'] ) ? $args['minimum_font_size'] : null;
$scale_factor = isset( $args['scale_factor'] ) ? $args['scale_factor'] : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should $scale_factor have a fallback value if not provided?


// Grab the minimum font size and normalize it 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.
$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.
$maximum_font_size = gutenberg_get_typography_value_and_unit(
$maximum_font_size_raw,
array(
'coerce_to' => $font_size_unit,
)
);

// Protect against unsupported units.
if ( ! $maximum_font_size || ! $minimum_font_size ) {
return null;
}

// Use rem for accessible fluid target font scaling.
$minimum_font_size_rem = gutenberg_get_typography_value_and_unit(
$minimum_font_size_raw,
array(
'coerce_to' => 'rem',
)
);

// Viewport widths defined for fluid typography. Normalize units.
$maximum_viewport_width = gutenberg_get_typography_value_and_unit(
$maximum_viewport_width_raw,
array(
'coerce_to' => $font_size_unit,
)
);
$minimum_viewport_width = gutenberg_get_typography_value_and_unit(
$minimum_viewport_width_raw,
array(
'coerce_to' => $font_size_unit,
)
);

// Build CSS rule.
// 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)";

return "clamp($minimum_font_size_raw, $fluid_target_font_size, $maximum_font_size_raw)";
}

/**
* Returns a font-size value based on a given font-size preset.
* Takes into account fluid typography parameters and attempts to return a css formula depending on available, valid values.
*
* @param array $preset fontSizes preset value as seen in theme.json.
* @param boolean $should_use_fluid_typography An override to switch fluid typography "on". Can be used for unit testing.
* @return string Font-size value.
*/
function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_typography = false ) {
// Check 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;

if ( ! $should_use_fluid_typography ) {
return $preset['size'];
}

// Defaults.
$default_maximum_viewport_width = '1600px';
$default_minimum_viewport_width = '768px';
$default_minimum_font_size_factor = 0.75;
$default_maximum_font_size_factor = 1.5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should default to 1. Then font sizes will stay as they are on larger screens, but rescale to be smaller on mobile.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, what if we set the scale to .5? My concern is that we don't want sizes to grow significantly on common viewport sizes.

Copy link
Member Author

@ramonjd ramonjd Jul 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should default to 1. Then font sizes will stay as they are on larger screens, but rescale to be smaller on mobile.

Alternatively, what if we set the scale to .5? My concern is that we don't want sizes to grow significantly on common viewport sizes.

I agree, in the absence of any fluid[min|max] values, the size property should still mean something when fluid typography is activated.

For example, it could represent the max (or min font size) value as you suggest. Or it could be the middle point. There's probably an argument for the suitability of all three where there are no explicit fluid min or max values.

We'd have to be careful not to allow font sizes to become too small, especially on mobile, but also not too large.

In general, I think it's something we can play with to achieve a balanced spread.

I'll land this and we can test further. Thanks a lot!

$default_scale_factor = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ignore my comment about default scale factor, you're using it here 😄


// Font sizes.
$fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null;

// Try to grab explicit min and max fluid font sizes.
$minimum_font_size_raw = isset( $fluid_font_size_settings['min'] ) ? $fluid_font_size_settings['min'] : null;
$maximum_font_size_raw = isset( $fluid_font_size_settings['max'] ) ? $fluid_font_size_settings['max'] : null;

// Font sizes.
$preferred_size = gutenberg_get_typography_value_and_unit( $preset['size'] );

// Protect against unsupported units.
if ( empty( $preferred_size['unit'] ) ) {
return $preset['size'];
}

// If no fluid min or max font sizes are available, create some using min/max font size factors.
if ( ! $minimum_font_size_raw ) {
$minimum_font_size_raw = ( $preferred_size['value'] * $default_minimum_font_size_factor ) . $preferred_size['unit'];
}

if ( ! $maximum_font_size_raw ) {
$maximum_font_size_raw = ( $preferred_size['value'] * $default_maximum_font_size_factor ) . $preferred_size['unit'];
}

$fluid_font_size_value = gutenberg_get_computed_fluid_typography_value(
array(
'minimum_viewport_width' => $default_minimum_viewport_width,
'maximum_viewport_width' => $default_maximum_viewport_width,
'minimum_font_size' => $minimum_font_size_raw,
'maximum_font_size' => $maximum_font_size_raw,
'scale_factor' => $default_scale_factor,
)
);

if ( ! empty( $fluid_font_size_value ) ) {
return $fluid_font_size_value;
}

return $preset['size'];
}

// Register the block support.
WP_Block_Supports::get_instance()->register(
'typography',
Expand Down
3 changes: 2 additions & 1 deletion lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null
'path' => array( 'typography', 'fontSizes' ),
'prevent_override' => false,
'use_default_names' => true,
'value_key' => 'size',
'value_func' => 'gutenberg_get_typography_font_size_value',
'css_vars' => '--wp--preset--font-size--$slug',
'classes' => array( '.has-$slug-font-size' => 'font-size' ),
'properties' => array( 'font-size' ),
Expand Down Expand Up @@ -1129,6 +1129,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null
'units' => null,
),
'typography' => array(
'fluid' => null,
'customFontSize' => null,
'dropCap' => null,
'fontFamilies' => null,
Expand Down
93 changes: 92 additions & 1 deletion phpunit/block-supports/typography-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function test_typography_with_skipped_serialization_block_supports() {
}

function test_letter_spacing_with_individual_skipped_serialization_block_supports() {
$this->test_block_name = 'test/letter-spacing-with-individua-skipped-serialization-block-supports';
$this->test_block_name = 'test/letter-spacing-with-individual-skipped-serialization-block-supports';
register_block_type(
$this->test_block_name,
array(
Expand Down Expand Up @@ -207,4 +207,95 @@ function test_font_family_with_class() {

$this->assertSame( $expected, $actual );
}

/**
* Tests generating font size values, including fluid formulae, from fontSizes preset.
*
* @dataProvider data_generate_font_size_preset_fixtures
*/
function test_gutenberg_get_typography_font_size_value( $font_size_preset, $should_use_fluid_typography, $expected_output ) {
$actual = gutenberg_get_typography_font_size_value( $font_size_preset, $should_use_fluid_typography );

$this->assertSame( $expected_output, $actual );
}

/**
* Data provider.
*
* @return array
*/
public function data_generate_font_size_preset_fixtures() {
return array(
'default_return_value' => array(
'font_size_preset' => array(
'size' => '28px',
),
'should_use_fluid_typography' => false,
'expected_output' => '28px',
),

'return_fluid_value' => array(
'font_size_preset' => array(
'size' => '1.75rem',
),
'should_use_fluid_typography' => true,
'expected_output' => 'clamp(1.3125rem, 1.3125rem + ((1vw - 0.48rem) * 2.524), 2.625rem)',
),

'return_default_fluid_values_with_empty_fluidSize' => array(
'font_size_preset' => array(
'size' => '28px',
'fluid' => array(),
),
'should_use_fluid_typography' => true,
'expected_output' => 'clamp(21px, 1.3125rem + ((1vw - 7.68px) * 2.524), 42px)',
),

'return_size_with_invalid_fluid_units' => array(
'font_size_preset' => array(
'size' => '10em',
'fluid' => array(
'min' => '20vw',
'max' => '50%',
),
),
'should_use_fluid_typography' => true,
'expected_output' => '10em',
),

'return_fluid_clamp_value' => array(
'font_size_preset' => array(
'size' => '28px',
'fluid' => array(
'min' => '20px',
'max' => '50rem',
),
),
'should_use_fluid_typography' => true,
'expected_output' => 'clamp(20px, 1.25rem + ((1vw - 7.68px) * 93.75), 50rem)',
),

'return_clamp_value_with_default_fluid_max_value' => array(
'font_size_preset' => array(
'size' => '28px',
'fluid' => array(
'min' => '2.6rem',
),
),
'should_use_fluid_typography' => true,
'expected_output' => 'clamp(2.6rem, 2.6rem + ((1vw - 0.48rem) * 0.048), 42px)',
),

'default_return_clamp_value_with_default_fluid_min_value' => array(
'font_size_preset' => array(
'size' => '28px',
'fluid' => array(
'max' => '80px',
),
),
'should_use_fluid_typography' => true,
'expected_output' => 'clamp(21px, 1.3125rem + ((1vw - 7.68px) * 7.091), 80px)',
),
);
}
}
3 changes: 3 additions & 0 deletions schemas/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@

## Unreleased

- Add new properties `settings.typography.fluid` and `settings.typography.fontSizes[n].fluidSize` to theme.json to enable fluid typography ([#39529](https://github.com/WordPress/gutenberg/pull/39529)).


Initial release.
18 changes: 18 additions & 0 deletions schemas/json/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@
"type": "boolean",
"default": true
},
"fluid": {
"description": "Opts into fluid typography.",
"type": "boolean"
},
"letterSpacing": {
"description": "Allow users to set custom letter spacing.",
"type": "boolean",
Expand Down Expand Up @@ -358,6 +362,20 @@
"size": {
"description": "CSS font-size value, including units.",
"type": "string"
},
"fluid": {
"type": "object",
"properties": {
"min": {
"description": "A min font size for fluid font size calculations in px, rem or em.",
"type": "string"
},
"max": {
"description": "A max font size for fluid font size calculations in px, rem or em.",
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
Expand Down