Skip to content

Commit

Permalink
Merge pull request #1701 from WordPress/update/pass-alignment-by-context
Browse files Browse the repository at this point in the history
Accurate sizes: Pass parent alignment context to images

Co-authored-by: joemcgill <[email protected]>
Co-authored-by: mukeshpanchal27 <[email protected]>
Co-authored-by: felixarntz <[email protected]>
Co-authored-by: westonruter <[email protected]>
  • Loading branch information
5 people authored Dec 12, 2024
2 parents d209703 + 7fb1962 commit 59e86f8
Show file tree
Hide file tree
Showing 3 changed files with 357 additions and 60 deletions.
2 changes: 2 additions & 0 deletions plugins/auto-sizes/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ function auto_sizes_render_generator(): void {
add_filter( 'the_content', 'auto_sizes_prime_attachment_caches', 9 ); // This must run before 'do_blocks', which runs at priority 9.
add_filter( 'render_block_core/image', 'auto_sizes_filter_image_tag', 10, 3 );
add_filter( 'render_block_core/cover', 'auto_sizes_filter_image_tag', 10, 3 );
add_filter( 'get_block_type_uses_context', 'auto_sizes_filter_uses_context', 10, 2 );
add_filter( 'render_block_context', 'auto_sizes_filter_render_block_context', 10, 2 );
216 changes: 168 additions & 48 deletions plugins/auto-sizes/includes/improve-calculate-sizes.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,6 @@
* @since n.e.x.t
*/

/**
* Gets the smaller image size if the layout width is bigger.
*
* It will return the smaller image size and return "px" if the layout width
* is something else, e.g. min(640px, 90vw) or 90vw.
*
* @since 1.1.0
*
* @param string $layout_width The layout width.
* @param int $image_width The image width.
* @return string The proper width after some calculations.
*/
function auto_sizes_get_width( string $layout_width, int $image_width ): string {
if ( str_ends_with( $layout_width, 'px' ) ) {
return $image_width > (int) $layout_width ? $layout_width : $image_width . 'px';
}
return $image_width . 'px';
}

/**
* Primes attachment into the cache with a single database query.
*
Expand Down Expand Up @@ -84,6 +65,7 @@ function auto_sizes_filter_image_tag( $content, array $parsed_block, WP_Block $b
if ( ! is_string( $content ) ) {
return '';
}

$processor = new WP_HTML_Tag_Processor( $content );
$has_image = $processor->next_tag( array( 'tag_name' => 'IMG' ) );

Expand All @@ -99,11 +81,24 @@ function auto_sizes_filter_image_tag( $content, array $parsed_block, WP_Block $b
* @param string $size The image size data.
*/
$filter = static function ( $sizes, $size ) use ( $block ) {
$id = $block->attributes['id'] ?? 0;
$alignment = $block->attributes['align'] ?? '';
$width = $block->attributes['width'] ?? '';

return auto_sizes_calculate_better_sizes( (int) $id, (string) $size, (string) $alignment, (string) $width );
$id = isset( $block->attributes['id'] ) ? (int) $block->attributes['id'] : 0;
$alignment = $block->attributes['align'] ?? '';
$width = isset( $block->attributes['width'] ) ? (int) $block->attributes['width'] : 0;
$max_alignment = $block->context['max_alignment'] ?? '';

/*
* Update width for cover block.
* See https://github.com/WordPress/gutenberg/blob/938720602082dc50a1746bd2e33faa3d3a6096d4/packages/block-library/src/cover/style.scss#L82-L87.
*/
if ( 'core/cover' === $block->name && in_array( $alignment, array( 'left', 'right' ), true ) ) {
$size = array( 420, 420 );
}

$better_sizes = auto_sizes_calculate_better_sizes( $id, $size, $alignment, $width, $max_alignment );

// If better sizes can't be calculated, use the default sizes.
return false !== $better_sizes ? $better_sizes : $sizes;
};

// Hook this filter early, before default filters are run.
Expand Down Expand Up @@ -135,50 +130,175 @@ function auto_sizes_filter_image_tag( $content, array $parsed_block, WP_Block $b
/**
* Modifies the sizes attribute of an image based on layout context.
*
* @param int $id The image id.
* @param string $size The image size data.
* @param string $align The image alignment.
* @param string $resize_width Resize image width.
* @return string The sizes attribute value.
* @since n.e.x.t
*
* @param int $id The image attachment post ID.
* @param string|array{int, int} $size Image size name or array of width and height.
* @param string $align The image alignment.
* @param int $resize_width Resize image width.
* @param string $max_alignment The maximum usable layout alignment.
* @return string|false An improved sizes attribute or false if a better size cannot be calculated.
*/
function auto_sizes_calculate_better_sizes( int $id, string $size, string $align, string $resize_width ): string {
$sizes = '';
$image = wp_get_attachment_image_src( $id, $size );
function auto_sizes_calculate_better_sizes( int $id, $size, string $align, int $resize_width, string $max_alignment ) {
// Without an image ID or a resize width, we cannot calculate a better size.
if ( 0 === $id && 0 === $resize_width ) {
return false;
}

if ( false === $image ) {
return $sizes;
$image_data = wp_get_attachment_image_src( $id, $size );

$image_width = false !== $image_data ? $image_data[1] : 0;

// If we don't have an image width or a resize width, we cannot calculate a better size.
if ( 0 === $image_width && 0 === $resize_width ) {
return false;
}

// Retrieve width from the image tag itself.
$image_width = '' !== $resize_width ? (int) $resize_width : $image[1];
/*
* If we don't have an image width, use the resize width.
* If we have both an image width and a resize width, use the smaller of the two.
*/
if ( 0 === $image_width ) {
$image_width = $resize_width;
} elseif ( 0 !== $resize_width ) {
$image_width = min( $image_width, $resize_width );
}

// Normalize default alignment values.
$align = '' !== $align ? $align : 'default';

/*
* Map alignment values to a weighting value so they can be compared.
* Note that 'left' and 'right' alignments are only constrained by max alignment.
*/
$constraints = array(
'full' => 0,
'wide' => 1,
'left' => 2,
'right' => 2,
'default' => 3,
'center' => 3,
);

$layout = wp_get_global_settings( array( 'layout' ) );
$alignment = $constraints[ $align ] > $constraints[ $max_alignment ] ? $align : $max_alignment;

// Handle different alignment use cases.
switch ( $align ) {
switch ( $alignment ) {
case 'full':
$sizes = '100vw';
$layout_width = auto_sizes_get_layout_width( 'full' );
break;

case 'wide':
if ( array_key_exists( 'wideSize', $layout ) ) {
$sizes = sprintf( '(max-width: %1$s) 100vw, %1$s', $layout['wideSize'] );
}
$layout_width = auto_sizes_get_layout_width( 'wide' );
break;

case 'left':
case 'right':
case 'center':
$sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $image_width );
$layout_width = sprintf( '%1$spx', $image_width );
break;

case 'center':
default:
if ( array_key_exists( 'contentSize', $layout ) ) {
$width = auto_sizes_get_width( $layout['contentSize'], $image_width );
$sizes = sprintf( '(max-width: %1$s) 100vw, %1$s', $width );
}
$alignment = auto_sizes_get_layout_width( 'default' );
$layout_width = sprintf( '%1$spx', min( (int) $alignment, $image_width ) );
break;
}

return $sizes;
// Format layout width when not 'full'.
if ( 'full' !== $alignment ) {
$layout_width = sprintf( '(max-width: %1$s) 100vw, %1$s', $layout_width );
}

return $layout_width;
}

/**
* Retrieves the layout width for an alignment defined in theme.json.
*
* @since n.e.x.t
*
* @param string $alignment The alignment value.
* @return string The alignment width based.
*/
function auto_sizes_get_layout_width( string $alignment ): string {
$layout = auto_sizes_get_layout_settings();

$layout_widths = array(
'full' => '100vw', // Todo: incorporate useRootPaddingAwareAlignments.
'wide' => array_key_exists( 'wideSize', $layout ) ? $layout['wideSize'] : '',
'default' => array_key_exists( 'contentSize', $layout ) ? $layout['contentSize'] : '',
);

return $layout_widths[ $alignment ] ?? '';
}

/**
* Filters the context keys that a block type uses.
*
* @since n.e.x.t
*
* @param string[] $uses_context Array of registered uses context for a block type.
* @param WP_Block_Type $block_type The full block type object.
* @return string[] The filtered context keys used by the block type.
*/
function auto_sizes_filter_uses_context( array $uses_context, WP_Block_Type $block_type ): array {
// The list of blocks that can consume outer layout context.
$consumer_blocks = array(
'core/cover',
'core/image',
);

if ( in_array( $block_type->name, $consumer_blocks, true ) ) {
// Use array_values to reset the array keys after merging.
return array_values( array_unique( array_merge( $uses_context, array( 'max_alignment' ) ) ) );
}
return $uses_context;
}

/**
* Modifies the block context during rendering to blocks.
*
* @since n.e.x.t
*
* @param array<string, mixed> $context Current block context.
* @param array<string, mixed> $block The block being rendered.
* @return array<string, mixed> Modified block context.
*/
function auto_sizes_filter_render_block_context( array $context, array $block ): array {
// When no max alignment is set, the maximum is assumed to be 'full'.
$context['max_alignment'] = $context['max_alignment'] ?? 'full';

// The list of blocks that can modify outer layout context.
$provider_blocks = array(
'core/columns',
'core/group',
);

if ( in_array( $block['blockName'], $provider_blocks, true ) ) {
$alignment = $block['attrs']['align'] ?? '';

// If the container block doesn't have alignment, it's assumed to be 'default'.
if ( '' === $alignment ) {
$context['max_alignment'] = 'default';
} elseif ( 'wide' === $alignment ) {
$context['max_alignment'] = 'wide';
}
}

return $context;
}

/**
* Retrieves the layout settings defined in theme.json.
*
* @since n.e.x.t
*
* @return array<string, mixed> Associative array of layout settings.
*/
function auto_sizes_get_layout_settings(): array {
static $layout = array();
if ( count( $layout ) === 0 ) {
$layout = wp_get_global_settings( array( 'layout' ) );
}
return $layout;
}
Loading

0 comments on commit 59e86f8

Please sign in to comment.