Make WordPress Core


Ignore:
Timestamp:
09/20/2022 03:41:44 PM (2 years ago)
Author:
hellofromTonya
Message:

Editor: Introduces fluid typography and uses Style Engine.

This commit introduces fluid typography block supports and switches to use the Style Engine for typography and colors.

The motivation for fluid typography block supports:

"Fluid typography" describes how a site's font sizes adapt to every change in screen size, for example, growing larger as the viewport width increases, or smaller as it decreases.

Font sizes can smoothly scale between minimum and maximum viewport widths.

Typography changes introduced from Gutenberg:

  • Uses the Style Engine to generate the CSS and classnames in wp_apply_typography_support().
  • Introduces wp_typography_get_preset_inline_style_value() for backwards-compatibility.
  • Introduces a private internal function called wp_get_typography_value_and_unit(), for checking and getting typography unit and value.
  • Introduces a private internal function called wp_get_computed_fluid_typography_value(), for an internal implementation of CSS clamp().
  • Deprecates wp_typography_get_css_variable_inline_style().

References:

Follow-up to [53076], [52302], [52069], [51089], [50761], [49226].

Props ramonopoly, youknowriad, aristath, oandregal, aaronrobertshaw, cbirdsong, jorgefilipecosta, ironprogrammer, hellofromTonya.
See #56467.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-supports/typography.php

    r53076 r54260  
    6666 *
    6767 * @since 5.6.0
     68 * @since 6.1.0 Used the style engine to generate CSS and classnames.
    6869 * @access private
    6970 *
     
    8586        return array();
    8687    }
    87 
    88     $attributes = array();
    89     $classes    = array();
    90     $styles     = array();
    9188
    9289    $has_font_family_support     = _wp_array_get( $typography_supports, array( '__experimentalFontFamily' ), false );
     
    9996    $has_text_transform_support  = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false );
    10097
    101     if ( $has_font_size_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontSize' ) ) {
    102         $has_named_font_size  = array_key_exists( 'fontSize', $block_attributes );
    103         $has_custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] );
    104 
    105         if ( $has_named_font_size ) {
    106             $classes[] = sprintf( 'has-%s-font-size', _wp_to_kebab_case( $block_attributes['fontSize'] ) );
    107         } elseif ( $has_custom_font_size ) {
    108             $styles[] = sprintf( 'font-size: %s;', $block_attributes['style']['typography']['fontSize'] );
    109         }
    110     }
    111 
    112     if ( $has_font_family_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontFamily' ) ) {
    113         $has_named_font_family  = array_key_exists( 'fontFamily', $block_attributes );
    114         $has_custom_font_family = isset( $block_attributes['style']['typography']['fontFamily'] );
    115 
    116         if ( $has_named_font_family ) {
    117             $classes[] = sprintf( 'has-%s-font-family', _wp_to_kebab_case( $block_attributes['fontFamily'] ) );
    118         } elseif ( $has_custom_font_family ) {
    119             // Before using classes, the value was serialized as a CSS Custom Property.
    120             // We don't need this code path when it lands in core.
    121             $font_family_custom = $block_attributes['style']['typography']['fontFamily'];
    122             if ( strpos( $font_family_custom, 'var:preset|font-family' ) !== false ) {
    123                 $index_to_splice    = strrpos( $font_family_custom, '|' ) + 1;
    124                 $font_family_slug   = _wp_to_kebab_case( substr( $font_family_custom, $index_to_splice ) );
    125                 $font_family_custom = sprintf( 'var(--wp--preset--font-family--%s)', $font_family_slug );
    126             }
    127             $styles[] = sprintf( 'font-family: %s;', $font_family_custom );
    128         }
    129     }
    130 
    131     if ( $has_font_style_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' ) ) {
    132         $font_style = wp_typography_get_css_variable_inline_style( $block_attributes, 'fontStyle', 'font-style' );
    133         if ( $font_style ) {
    134             $styles[] = $font_style;
    135         }
    136     }
    137 
    138     if ( $has_font_weight_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' ) ) {
    139         $font_weight = wp_typography_get_css_variable_inline_style( $block_attributes, 'fontWeight', 'font-weight' );
    140         if ( $font_weight ) {
    141             $styles[] = $font_weight;
    142         }
    143     }
    144 
    145     if ( $has_line_height_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' ) ) {
    146         $has_line_height = isset( $block_attributes['style']['typography']['lineHeight'] );
    147         if ( $has_line_height ) {
    148             $styles[] = sprintf( 'line-height: %s;', $block_attributes['style']['typography']['lineHeight'] );
    149         }
    150     }
    151 
    152     if ( $has_text_decoration_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' ) ) {
    153         $text_decoration_style = wp_typography_get_css_variable_inline_style( $block_attributes, 'textDecoration', 'text-decoration' );
    154         if ( $text_decoration_style ) {
    155             $styles[] = $text_decoration_style;
    156         }
    157     }
    158 
    159     if ( $has_text_transform_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' ) ) {
    160         $text_transform_style = wp_typography_get_css_variable_inline_style( $block_attributes, 'textTransform', 'text-transform' );
    161         if ( $text_transform_style ) {
    162             $styles[] = $text_transform_style;
    163         }
    164     }
    165 
    166     if ( $has_letter_spacing_support && ! wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' ) ) {
    167         $letter_spacing_style = wp_typography_get_css_variable_inline_style( $block_attributes, 'letterSpacing', 'letter-spacing' );
    168         if ( $letter_spacing_style ) {
    169             $styles[] = $letter_spacing_style;
    170         }
    171     }
    172 
    173     if ( ! empty( $classes ) ) {
    174         $attributes['class'] = implode( ' ', $classes );
    175     }
    176     if ( ! empty( $styles ) ) {
    177         $attributes['style'] = implode( ' ', $styles );
     98    // Whether to skip individual block support features.
     99    $should_skip_font_size       = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontSize' );
     100    $should_skip_font_family     = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontFamily' );
     101    $should_skip_font_style      = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' );
     102    $should_skip_font_weight     = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' );
     103    $should_skip_line_height     = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' );
     104    $should_skip_text_decoration = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' );
     105    $should_skip_text_transform  = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' );
     106    $should_skip_letter_spacing  = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' );
     107
     108    $typography_block_styles = array();
     109    if ( $has_font_size_support && ! $should_skip_font_size ) {
     110        $preset_font_size                    = array_key_exists( 'fontSize', $block_attributes )
     111            ? "var:preset|font-size|{$block_attributes['fontSize']}"
     112            : null;
     113        $custom_font_size                    = isset( $block_attributes['style']['typography']['fontSize'] )
     114            ? $block_attributes['style']['typography']['fontSize']
     115            : null;
     116        $typography_block_styles['fontSize'] = $preset_font_size ? $preset_font_size : $custom_font_size;
     117    }
     118
     119    if ( $has_font_family_support && ! $should_skip_font_family ) {
     120        $preset_font_family                    = array_key_exists( 'fontFamily', $block_attributes )
     121            ? "var:preset|font-family|{$block_attributes['fontFamily']}"
     122            : null;
     123        $custom_font_family                    = isset( $block_attributes['style']['typography']['fontFamily'] )
     124            ? wp_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['fontFamily'], 'font-family' )
     125            : null;
     126        $typography_block_styles['fontFamily'] = $preset_font_family ? $preset_font_family : $custom_font_family;
     127    }
     128
     129    if (
     130        $has_font_style_support &&
     131        ! $should_skip_font_style &&
     132        isset( $block_attributes['style']['typography']['fontStyle'] )
     133    ) {
     134        $typography_block_styles['fontStyle'] = wp_typography_get_preset_inline_style_value(
     135            $block_attributes['style']['typography']['fontStyle'],
     136            'font-style'
     137        );
     138    }
     139
     140    if (
     141        $has_font_weight_support &&
     142        ! $should_skip_font_weight &&
     143        isset( $block_attributes['style']['typography']['fontWeight'] )
     144    ) {
     145        $typography_block_styles['fontWeight'] = wp_typography_get_preset_inline_style_value(
     146            $block_attributes['style']['typography']['fontWeight'],
     147            'font-weight'
     148        );
     149    }
     150
     151    if ( $has_line_height_support && ! $should_skip_line_height ) {
     152        $typography_block_styles['lineHeight'] = _wp_array_get( $block_attributes, array( 'style', 'typography', 'lineHeight' ) );
     153    }
     154
     155    if (
     156        $has_text_decoration_support &&
     157        ! $should_skip_text_decoration &&
     158        isset( $block_attributes['style']['typography']['textDecoration'] )
     159    ) {
     160        $typography_block_styles['textDecoration'] = wp_typography_get_preset_inline_style_value(
     161            $block_attributes['style']['typography']['textDecoration'],
     162            'text-decoration'
     163        );
     164    }
     165
     166    if (
     167        $has_text_transform_support &&
     168        ! $should_skip_text_transform &&
     169        isset( $block_attributes['style']['typography']['textTransform'] )
     170    ) {
     171        $typography_block_styles['textTransform'] = wp_typography_get_preset_inline_style_value(
     172            $block_attributes['style']['typography']['textTransform'],
     173            'text-transform'
     174        );
     175    }
     176
     177    if (
     178        $has_letter_spacing_support &&
     179        ! $should_skip_letter_spacing &&
     180        isset( $block_attributes['style']['typography']['letterSpacing'] )
     181    ) {
     182        $typography_block_styles['letterSpacing'] = wp_typography_get_preset_inline_style_value(
     183            $block_attributes['style']['typography']['letterSpacing'],
     184            'letter-spacing'
     185        );
     186    }
     187
     188    $attributes = array();
     189    $styles     = wp_style_engine_get_styles(
     190        array( 'typography' => $typography_block_styles ),
     191        array( 'convert_vars_to_classnames' => true )
     192    );
     193
     194    if ( ! empty( $styles['classnames'] ) ) {
     195        $attributes['class'] = $styles['classnames'];
     196    }
     197
     198    if ( ! empty( $styles['css'] ) ) {
     199        $attributes['style'] = $styles['css'];
    178200    }
    179201
     
    182204
    183205/**
    184  * Generates an inline style for a typography feature e.g. text decoration,
     206 * Generates an inline style value for a typography feature e.g. text decoration,
    185207 * text transform, and font style.
    186208 *
    187  * @since 5.8.0
     209 * Note: This function is for backwards compatibility.
     210 * * It is necessary to parse older blocks whose typography styles contain presets.
     211 * * It mostly replaces the deprecated `wp_typography_get_css_variable_inline_style()`,
     212 *   but skips compiling a CSS declaration as the style engine takes over this role.
     213 * @link https://github.com/wordpress/gutenberg/pull/27555
     214 *
     215 * @since 6.1.0
     216 *
     217 * @param string $style_value  A raw style value for a single typography feature from a block's style attribute.
     218 * @param string $css_property Slug for the CSS property the inline style sets.
     219 * @return string A CSS inline style value.
     220 */
     221function wp_typography_get_preset_inline_style_value( $style_value, $css_property ) {
     222    // If the style value is not a preset CSS variable go no further.
     223    if ( empty( $style_value ) || ! str_contains( $style_value, "var:preset|{$css_property}|" ) ) {
     224        return $style_value;
     225    }
     226
     227    /*
     228     * For backwards compatibility.
     229     * Presets were removed in WordPress/gutenberg#27555.
     230     * A preset CSS variable is the style.
     231     * Gets the style value from the string and return CSS style.
     232     */
     233    $index_to_splice = strrpos( $style_value, '|' ) + 1;
     234    $slug            = _wp_to_kebab_case( substr( $style_value, $index_to_splice ) );
     235
     236    // Return the actual CSS inline style value,
     237    // e.g. `var(--wp--preset--text-decoration--underline);`.
     238    return sprintf( 'var(--wp--preset--%s--%s);', $css_property, $slug );
     239}
     240
     241/**
     242 * Checks a string for a unit and value and returns an array
     243 * consisting of `'value'` and `'unit'`, e.g., [ '42', 'rem' ].
     244 *
     245 * @since 6.1.0
    188246 * @access private
    189247 *
    190  * @param array  $attributes   Block's attributes.
    191  * @param string $feature      Key for the feature within the typography styles.
    192  * @param string $css_property Slug for the CSS property the inline style sets.
    193  * @return string CSS inline style.
    194  */
    195 function wp_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) {
    196     // Retrieve current attribute value or skip if not found.
    197     $style_value = _wp_array_get( $attributes, array( 'style', 'typography', $feature ), false );
    198     if ( ! $style_value ) {
    199         return;
    200     }
    201 
    202     // If we don't have a preset CSS variable, we'll assume it's a regular CSS value.
    203     if ( strpos( $style_value, "var:preset|{$css_property}|" ) === false ) {
    204         return sprintf( '%s:%s;', $css_property, $style_value );
    205     }
    206 
    207     // We have a preset CSS variable as the style.
    208     // Get the style value from the string and return CSS style.
    209     $index_to_splice = strrpos( $style_value, '|' ) + 1;
    210     $slug            = substr( $style_value, $index_to_splice );
    211 
    212     // Return the actual CSS inline style e.g. `text-decoration:var(--wp--preset--text-decoration--underline);`.
    213     return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug );
     248 * @param string $raw_value Raw size value from theme.json.
     249 * @param array  $options   {
     250 *     Optional. An associative array of options. Default is empty array.
     251 *
     252 *     @type string   $coerce_to        Coerce the value to rem or px. Default `'rem'`.
     253 *     @type int      $root_size_value  Value of root font size for rem|em <-> px conversion. Default `16`.
     254 *     @type string[] $acceptable_units An array of font size units. Default `[ 'rem', 'px', 'em' ]`;
     255 * }
     256 * @return array|null An array consisting of `'value'` and `'unit'` properties on success.
     257 *                    `null` on failure.
     258 */
     259function wp_get_typography_value_and_unit( $raw_value, $options = array() ) {
     260    if ( empty( $raw_value ) ) {
     261        return null;
     262    }
     263
     264    $defaults = array(
     265        'coerce_to'        => '',
     266        'root_size_value'  => 16,
     267        'acceptable_units' => array( 'rem', 'px', 'em' ),
     268    );
     269
     270    $options = wp_parse_args( $options, $defaults );
     271
     272    $acceptable_units_group = implode( '|', $options['acceptable_units'] );
     273    $pattern                = '/^(\d*\.?\d+)(' . $acceptable_units_group . '){1,1}$/';
     274
     275    preg_match( $pattern, $raw_value, $matches );
     276
     277    // Bails out if not a number value and a px or rem unit.
     278    if ( ! isset( $matches[1] ) || ! isset( $matches[2] ) ) {
     279        return null;
     280    }
     281
     282    $value = $matches[1];
     283    $unit  = $matches[2];
     284
     285    // Default browser font size. Later, possibly could inject some JS to
     286    // compute this `getComputedStyle( document.querySelector( "html" ) ).fontSize`.
     287    if ( 'px' === $options['coerce_to'] && ( 'em' === $unit || 'rem' === $unit ) ) {
     288        $value = $value * $options['root_size_value'];
     289        $unit  = $options['coerce_to'];
     290    }
     291
     292    if ( 'px' === $unit && ( 'em' === $options['coerce_to'] || 'rem' === $options['coerce_to'] ) ) {
     293        $value = $value / $options['root_size_value'];
     294        $unit  = $options['coerce_to'];
     295    }
     296
     297    return array(
     298        'value' => $value,
     299        'unit'  => $unit,
     300    );
     301}
     302
     303/**
     304 * Internal implementation of CSS clamp() based on available min/max viewport
     305 * width and min/max font sizes.
     306 *
     307 * @since 6.1.0
     308 * @access private
     309 *
     310 * @param array $args {
     311 *     Optional. An associative array of values to calculate a fluid formula
     312 *     for font size. Default is empty array.
     313 *
     314 *     @type string $maximum_viewport_width Maximum size up to which type will have fluidity.
     315 *     @type string $minimum_viewport_width Minimum viewport size from which type will have fluidity.
     316 *     @type string $maximum_font_size      Maximum font size for any clamp() calculation.
     317 *     @type string $minimum_font_size      Minimum font size for any clamp() calculation.
     318 *     @type int    $scale_factor           A scale factor to determine how fast a font scales within boundaries.
     319 * }
     320 * @return string|null A font-size value using clamp() on success. Else, null.
     321 */
     322function wp_get_computed_fluid_typography_value( $args = array() ) {
     323    $maximum_viewport_width_raw = isset( $args['maximum_viewport_width'] ) ? $args['maximum_viewport_width'] : null;
     324    $minimum_viewport_width_raw = isset( $args['minimum_viewport_width'] ) ? $args['minimum_viewport_width'] : null;
     325    $maximum_font_size_raw      = isset( $args['maximum_font_size'] ) ? $args['maximum_font_size'] : null;
     326    $minimum_font_size_raw      = isset( $args['minimum_font_size'] ) ? $args['minimum_font_size'] : null;
     327    $scale_factor               = isset( $args['scale_factor'] ) ? $args['scale_factor'] : null;
     328
     329    // Grab the minimum font size and normalize it in order to use the value for calculations.
     330    $minimum_font_size = wp_get_typography_value_and_unit( $minimum_font_size_raw );
     331
     332    // We get a 'preferred' unit to keep units consistent when calculating, otherwise the result will not be accurate.
     333    $font_size_unit = isset( $minimum_font_size['unit'] ) ? $minimum_font_size['unit'] : 'rem';
     334
     335    // Grab the maximum font size and normalize it in order to use the value for calculations.
     336    $maximum_font_size = wp_get_typography_value_and_unit(
     337        $maximum_font_size_raw,
     338        array(
     339            'coerce_to' => $font_size_unit,
     340        )
     341    );
     342
     343    // Protect against unsupported units.
     344    if ( ! $maximum_font_size || ! $minimum_font_size ) {
     345        return null;
     346    }
     347
     348    // Use rem for accessible fluid target font scaling.
     349    $minimum_font_size_rem = wp_get_typography_value_and_unit(
     350        $minimum_font_size_raw,
     351        array(
     352            'coerce_to' => 'rem',
     353        )
     354    );
     355
     356    // Viewport widths defined for fluid typography. Normalize units.
     357    $maximum_viewport_width = wp_get_typography_value_and_unit(
     358        $maximum_viewport_width_raw,
     359        array(
     360            'coerce_to' => $font_size_unit,
     361        )
     362    );
     363    $minimum_viewport_width = wp_get_typography_value_and_unit(
     364        $minimum_viewport_width_raw,
     365        array(
     366            'coerce_to' => $font_size_unit,
     367        )
     368    );
     369
     370    /*
     371     * Build CSS rule.
     372     * Borrowed from https://websemantics.uk/tools/responsive-font-calculator/.
     373     */
     374    $view_port_width_offset = round( $minimum_viewport_width['value'] / 100, 3 ) . $font_size_unit;
     375    $linear_factor          = 100 * ( ( $maximum_font_size['value'] - $minimum_font_size['value'] ) / ( $maximum_viewport_width['value'] - $minimum_viewport_width['value'] ) );
     376    $linear_factor          = round( $linear_factor, 3 ) * $scale_factor;
     377    $fluid_target_font_size = implode( '', $minimum_font_size_rem ) . " + ((1vw - $view_port_width_offset) * $linear_factor)";
     378
     379    return "clamp($minimum_font_size_raw, $fluid_target_font_size, $maximum_font_size_raw)";
     380}
     381
     382/**
     383 * Returns a font-size value based on a given font-size preset.
     384 * Takes into account fluid typography parameters and attempts to return a CSS
     385 * formula depending on available, valid values.
     386 *
     387 * @since 6.1.0
     388 *
     389 * @param array $preset                     {
     390 *     Required. fontSizes preset value as seen in theme.json.
     391 *
     392 *     @type string $name Name of the font size preset.
     393 *     @type string $slug Kebab-case unique identifier for the font size preset.
     394 *     @type string $size CSS font-size value, including units where applicable.
     395 * }
     396 * @param bool  $should_use_fluid_typography An override to switch fluid typography "on". Can be used for unit testing.
     397 *                                           Default is `false`.
     398 * @return string Font-size value.
     399 */
     400function wp_get_typography_font_size_value( $preset, $should_use_fluid_typography = false ) {
     401    // Checks if fluid font sizes are activated.
     402    $typography_settings         = wp_get_global_settings( array( 'typography' ) );
     403    $should_use_fluid_typography = isset( $typography_settings['fluid'] ) && true === $typography_settings['fluid'] ? true : $should_use_fluid_typography;
     404
     405    if ( ! $should_use_fluid_typography ) {
     406        return $preset['size'];
     407    }
     408
     409    // Defaults.
     410    $default_maximum_viewport_width   = '1600px';
     411    $default_minimum_viewport_width   = '768px';
     412    $default_minimum_font_size_factor = 0.75;
     413    $default_maximum_font_size_factor = 1.5;
     414    $default_scale_factor             = 1;
     415
     416    // Font sizes.
     417    $fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null;
     418
     419    // A font size has explicitly bypassed fluid calculations.
     420    if ( false === $fluid_font_size_settings ) {
     421        return $preset['size'];
     422    }
     423
     424    // Try to grab explicit min and max fluid font sizes.
     425    $minimum_font_size_raw = isset( $fluid_font_size_settings['min'] ) ? $fluid_font_size_settings['min'] : null;
     426    $maximum_font_size_raw = isset( $fluid_font_size_settings['max'] ) ? $fluid_font_size_settings['max'] : null;
     427
     428    // Font sizes.
     429    $preferred_size = wp_get_typography_value_and_unit( $preset['size'] );
     430
     431    // Protect against unsupported units.
     432    if ( empty( $preferred_size['unit'] ) ) {
     433        return $preset['size'];
     434    }
     435
     436    // If no fluid min or max font sizes are available, create some using min/max font size factors.
     437    if ( ! $minimum_font_size_raw ) {
     438        $minimum_font_size_raw = ( $preferred_size['value'] * $default_minimum_font_size_factor ) . $preferred_size['unit'];
     439    }
     440
     441    if ( ! $maximum_font_size_raw ) {
     442        $maximum_font_size_raw = ( $preferred_size['value'] * $default_maximum_font_size_factor ) . $preferred_size['unit'];
     443    }
     444
     445    $fluid_font_size_value = wp_get_computed_fluid_typography_value(
     446        array(
     447            'minimum_viewport_width' => $default_minimum_viewport_width,
     448            'maximum_viewport_width' => $default_maximum_viewport_width,
     449            'minimum_font_size'      => $minimum_font_size_raw,
     450            'maximum_font_size'      => $maximum_font_size_raw,
     451            'scale_factor'           => $default_scale_factor,
     452        )
     453    );
     454
     455    if ( ! empty( $fluid_font_size_value ) ) {
     456        return $fluid_font_size_value;
     457    }
     458
     459    return $preset['size'];
    214460}
    215461
Note: See TracChangeset for help on using the changeset viewer.