Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -425,7 +425,7 @@ private function emitProjectedStyleRule(string $prelude, string $body, AuthorSty
$svgImagePrelude = $this->projectAuthorImageSelectorPrelude($prelude, $context, 'svg', $declarations);
$imageRule = '' === $imagePrelude
? ''
: $imagePrelude . '{' . $this->imageProjectionBridgeDeclarations($declarations) . '}';
: $this->imageProjectionBridgeRule($prelude, $imagePrelude, $declarations, $context);
if ('' !== $imagePrelude) {
// The generic Image bridge introduces block display and responsive
// shrinkage. An inline source image in an auto table must instead
Expand Down Expand Up @@ -3335,8 +3335,44 @@ private function lineImageFigureRule(string $imagePrelude, array $declarations,
. ( isset($declarations['vertical-align']) ? ';vertical-align:' . $declarations['vertical-align'] : '' ) . '}';
}

/**
* The bridge lets the native image follow the figure that now carries the
* source rule. A selector whose subject is a bare `img` has nothing that
* moves to the figure, so the source rule still paints the image itself:
* inheriting the figure's corners there would replace the image's own
* radius at the same specificity, later in the cascade.
*
* @param array<string, string> $declarations
*/
private function imageProjectionBridgeRule(string $prelude, string $imagePrelude, array $declarations, AuthorStylesheetProjectionContext $context): string
{
$selectors = CssStylesheetTransformer::splitSelectorList($prelude) ?? array();
$moved = array_values(array_filter($selectors, fn (string $selector): bool => ! $this->subjectStaysOnImage($selector, $context)));
if ( count($moved) === count($selectors) ) {
return $imagePrelude . '{' . $this->imageProjectionBridgeDeclarations($declarations) . '}';
}
$rule = $imagePrelude . '{' . $this->imageProjectionBridgeDeclarations($declarations, false, false) . '}';
$movedPrelude = array() === $moved ? '' : $this->projectAuthorImageSelectorPrelude(implode(',', $moved), $context);

return '' === $movedPrelude ? $rule : $rule . $movedPrelude . '{border-radius:inherit}';
}

private function subjectStaysOnImage(string $selector, AuthorStylesheetProjectionContext $context): bool
{
$parsed = $context->sourceStyles->parsedSelector($selector);
if ( ! $parsed['supported'] ) {
return false;
}
$subject = CssSelectorCompoundInspector::rightmost($parsed['compounds']);

return 'img' === strtolower((string) ($subject['type'] ?? ''))
&& array() === ($subject['classes'] ?? array())
&& array() === ($subject['ids'] ?? array())
&& array() === ($subject['attributes'] ?? array());
}

/** @param array<string, string> $declarations */
private function imageProjectionBridgeDeclarations(array $declarations, bool $preserveObjectFit = false): string
private function imageProjectionBridgeDeclarations(array $declarations, bool $preserveObjectFit = false, bool $inheritRadius = true): string
{
$bridge = array( 'display:block' );
$position = strtolower(trim((string) ($declarations['position'] ?? '')));
Expand All @@ -3362,7 +3398,9 @@ private function imageProjectionBridgeDeclarations(array $declarations, bool $pr
$objectFit = $this->explicitObjectFit($declarations);
$bridge[] = 'object-fit:' . ($preserveObjectFit && '' !== $objectFit ? $objectFit : 'inherit');
$bridge[] = 'object-position:inherit';
$bridge[] = 'border-radius:inherit';
if ( $inheritRadius ) {
$bridge[] = 'border-radius:inherit';
}
return implode(';', $bridge);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,35 @@
$assert(str_contains($positionedImageCss, 'object-fit:fill !important;object-position:-197.702px -102.702px !important'), 'image presentation carrier preserves source object fit and position for the nested image bridge');
$assert(str_contains($positionedImageCss, '.map.wp-block-image > img,.map.wp-block-image > a > img{display:block;width:100%;height:100%;max-width:100%;object-fit:inherit;object-position:inherit;border-radius:inherit}'), 'nested image fills a wrapper with explicitly owned width and height, linked or not');

// Corner radius follows whichever box the source rule paints after projection:
// the figure when the image's class moved onto it, the image itself when the
// rule's subject is a bare `img`.
foreach (array(
'class moved to figure' => array('<img class="photo" src="p.jpg" alt="a">', '.photo{width:120px;height:120px;border-radius:50%}'),
'descendant img subject' => array('<div class="avatar"><img src="p.jpg" alt="a"></div>', '.avatar img{width:120px;height:120px;border-radius:50%}'),
'later radius-less breakpoint rule' => array('<div class="avatar"><img src="p.jpg" alt="a"></div>', '.avatar img{width:120px;height:120px;border-radius:50%}@media (min-width:1px){.avatar img{width:100px;height:100px}}'),
'mixed selector list' => array('<img class="photo" src="p.jpg" alt="a"><div class="avatar"><img src="p.jpg" alt="b"></div>', '.photo,.avatar img{width:120px;height:120px;border-radius:50%}'),
) as $case => [$radiusHtml, $radiusCss]) {
$radius = ( new ArtifactCompiler() )->compile(array(
'files' => array(
array( 'path' => 'index.html', 'kind' => 'html', 'content' => '<link rel="stylesheet" href="r.css">' . $radiusHtml ),
array( 'path' => 'r.css', 'kind' => 'css', 'content_base64' => base64_encode($radiusCss) ),
array( 'path' => 'p.jpg', 'kind' => 'image', 'content' => 'image-bytes' ),
),
) )->toArray();
$radiusDocument = new DOMDocument();
@$radiusDocument->loadHTML('<body>' . preg_replace('/<!--.*?-->/s', '', (string) ($radius['serialized_blocks'] ?? '')) . '</body>');
$radiusCascade = new StaticCssCascade($radiusDocument, implode("\n", array_column($radius['assets'] ?? array(), 'content')));
$radii = array();
foreach ( $radiusDocument->getElementsByTagName('img') as $radiusImage ) {
$value = $radiusCascade->resolve($radiusImage, array( 'border-radius' ), array())['border-radius'] ?? '';
$radii[] = 'inherit' === $value && $radiusImage->parentNode instanceof DOMElement
? ($radiusCascade->resolve($radiusImage->parentNode, array( 'border-radius' ), array())['border-radius'] ?? '')
: $value;
}
$assert(array() !== $radii && array( '50%' ) === array_values(array_unique($radii)), 'projected image keeps its source corner radius: ' . $case . ' (got ' . implode(', ', $radii) . ')');
}

$multiPage = ( new ArtifactCompiler() )->compile(array(
'entrypoint' => 'index.html',
'files' => array(
Expand Down
Loading