Skip to content
Merged
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
42 changes: 41 additions & 1 deletion php-transformer/src/HtmlToBlocks/HtmlCompilation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3275,7 +3275,10 @@ public function createBlock(string $name, array $attrs = array(), array $innerBl
$hasAuthorControlProjection,
'core/button' === $name && '' !== $logicalControlPath && $hasAuthorControlProjection
&& $this->isDirectChildOfAuthorFlexLayout($logicalControl),
$preserveGeneratedStyle
$preserveGeneratedStyle,
'core/image' === $name
&& 'figure' !== $sourceTagName
&& $this->syntheticImageFigureFollowsInlineFlow($sourceElement)
),
$this->sourceBlockAttributeProjectionContext()
);
Expand Down Expand Up @@ -10060,6 +10063,43 @@ private function wrapInlineLink(string $content, array $linkAttrs): string
* @param array<string, mixed> $attrs
* @return array<string, mixed>
*/
/**
* Whether a synthesized image figure stands in for inline content that the
* parent aligns. Only an alignment that acts on inline content matters: a
* block image is already positioned by its own box, and shrink-wrapping it
* would strip the width its margins resolve against.
*/
private function syntheticImageFigureFollowsInlineFlow(DOMElement $element): bool
{
$display = $this->resolvedDeclaration($element, 'display');
if ( '' !== $display && ! in_array($display, array( 'inline', 'inline-block' ), true) ) {
return false;
}

// text-align inherits, so the alignment that governs this image can be
// declared on any ancestor above the inline wrappers it sits in.
$node = $element->parentNode;
for ( $depth = 0; $depth < 8 && $node instanceof DOMElement; ++$depth ) {
$align = $this->resolvedDeclaration($node, 'text-align');
if ( '' !== $align ) {
return in_array($align, array( 'right', 'center', 'end' ), true);
}
$node = $node->parentNode;
}

return false;
}

private function resolvedDeclaration(DOMElement $element, string $property): string
{
$resolved = $this->styleResolver->resolveCssVariablesInValue(
$this->styleResolver->specificityResolvedPresentationStyle($element)
);
$declarations = $this->styleResolver->cssDeclarations($resolved);

return strtolower(trim($this->cssValueWithoutImportant((string) ($declarations[ $property ] ?? ''))));
}

private function rebuildBlock(array $block, array $attrs): array
{
$name = (string) ($block['blockName'] ?? '');
Expand Down
3 changes: 3 additions & 0 deletions php-transformer/src/HtmlToBlocks/Style/EngineSupportCss.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public function beforeAuthorCss(string $serializedBlocks, string $layoutShellBlo
if ( str_contains($serializedBlocks, SourceBlockAttributeProjector::SYNTHETIC_IMAGE_FIGURE_CLASS) ) {
$parts[] = '.' . SourceBlockAttributeProjector::SYNTHETIC_IMAGE_FIGURE_CLASS . '{margin:0}';
}
if ( str_contains($serializedBlocks, SourceBlockAttributeProjector::SYNTHETIC_INLINE_IMAGE_FIGURE_CLASS) ) {
$parts[] = ':root .' . SourceBlockAttributeProjector::SYNTHETIC_INLINE_IMAGE_FIGURE_CLASS . '{display:inline-block}';
}
if ( str_contains($serializedBlocks, self::BACKGROUND_IMAGE_CLASS) ) {
// The source painted this image as a background, where the element's
// own box decides the size and the image never overflows it. core's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public function __construct(
public readonly bool $isAuthorLayoutItem,
public readonly bool $hasAuthorControlProjection,
public readonly bool $isDirectChildOfAuthorFlexLayout,
public readonly bool $preserveGeneratedStyle
public readonly bool $preserveGeneratedStyle,
public readonly bool $syntheticImageFigureFollowsInlineFlow
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final class SourceBlockAttributeProjector
public const HIDDEN_RICH_TEXT_MARKER_CLASS = 'blocks-engine-hidden-richtext-marker';
public const SYNTHETIC_ANCHOR_UNDECORATED_CLASS = 'blocks-engine-synthetic-anchor-undecorated';
public const SYNTHETIC_IMAGE_FIGURE_CLASS = 'blocks-engine-synthetic-image-figure';
public const SYNTHETIC_INLINE_IMAGE_FIGURE_CLASS = 'blocks-engine-synthetic-image-figure-inline';
public const CSS_OWNED_INLINE_FLOW_CLASS = 'blocks-engine-css-owned-inline-flow';
public const CSS_OWNED_LAYOUT_ITEM_CLASS = 'blocks-engine-css-owned-layout-item';

Expand Down Expand Up @@ -42,6 +43,12 @@ public function project(
$sourceTagName = strtolower($sourceElement->tagName);
if ( 'core/image' === $name && 'figure' !== $sourceTagName ) {
$attrs['className'] = SourceDom::mergeClassNames((string) ($attrs['className'] ?? ''), self::SYNTHETIC_IMAGE_FIGURE_CLASS);
// The source image was inline content that its parent aligned. A
// synthesized figure is a block box that fills the line instead,
// so the alignment has nothing left to move.
if ( $facts->syntheticImageFigureFollowsInlineFlow ) {
$attrs['className'] = SourceDom::mergeClassNames((string) ($attrs['className'] ?? ''), self::SYNTHETIC_INLINE_IMAGE_FIGURE_CLASS);
}
}
if ( 'core/paragraph' === $name && $facts->isInlineSourceElement ) {
$attrs['className'] = SourceDom::mergeClassNames((string) ($attrs['className'] ?? ''), self::SYNTHETIC_PARAGRAPH_CLASS);
Expand Down
93 changes: 93 additions & 0 deletions php-transformer/tests/unit/synthetic-image-figure-inline-flow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);

/**
* A bare <img> is inline content, so its parent's text-align decides where it
* sits. Wrapping it in a synthesized <figure> replaces that inline box with a
* block box that fills the line, and the alignment has nothing left to move.
*
* Only an alignment that acts on inline content matters here: a block image is
* positioned by its own box, and shrink-wrapping it would strip the width its
* auto margins resolve against.
*/

require dirname(__DIR__, 2) . '/vendor/autoload.php';

use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\HtmlTransformer;

$failures = 0;
$passes = 0;
$assert = static function (bool $ok, string $message, string $detail = '') use (&$failures, &$passes): void {
if ( $ok ) {
++$passes;
return;
}
++$failures;
fwrite(STDERR, 'FAIL: ' . $message . ( '' !== $detail ? ' - ' . $detail : '' ) . PHP_EOL);
};

$transform = static function (string $html): array {
$result = ( new HtmlTransformer() )->transform($html, array())->toArray();
$css = '';
foreach ( $result['assets'] ?? array() as $asset ) {
if ( is_array($asset) && 'css' === ($asset['kind'] ?? null) ) {
$css .= (string) ($asset['content'] ?? '');
}
}

return array( (string) ($result['serialized_blocks'] ?? ''), $css );
};

$inlineClass = 'blocks-engine-synthetic-image-figure-inline';

// Right-aligned inline image, with the anchor wrapper the source usually has.
list( $right, $rightCss ) = $transform('<main><div style="text-align:right"><a><img src="/a.jpg" alt="P"></a></div></main>');
$assert(
str_contains($right, $inlineClass),
'a right-aligned inline image keeps an inline-level figure so the alignment still moves it',
$right
);
$assert(
str_contains($rightCss, '.' . $inlineClass . '{display:inline-block}'),
'the inline-level synthetic figure ships its display rule',
$rightCss
);

// Centered inline image.
list( $center, ) = $transform('<main><div style="text-align:center"><img src="/a.jpg" alt="P"></div></main>');
$assert(
str_contains($center, $inlineClass),
'a centered inline image keeps an inline-level figure',
$center
);

// Left alignment is what a block figure already does, so nothing changes.
list( $left, ) = $transform('<main><div style="text-align:left"><a><img src="/a.jpg" alt="P"></a></div></main>');
$assert(
! str_contains($left, $inlineClass),
'a left-aligned image is left as an ordinary block figure',
$left
);

// A block image is positioned by its own box; shrink-wrapping it would break
// the width its auto margins resolve against.
list( $blockImage, ) = $transform('<style>img{display:block;margin:0 auto}</style><main><div style="text-align:center"><img src="/a.jpg" alt="P"></div></main>');
$assert(
! str_contains($blockImage, $inlineClass),
'a block image centered by auto margins keeps its full-width figure',
$blockImage
);

// An authored <figure> is not synthesized at all.
list( $authored, ) = $transform('<main><figure style="text-align:right"><img src="/a.jpg" alt="P"></figure></main>');
$assert(
! str_contains($authored, 'blocks-engine-synthetic-image-figure'),
'an authored figure is never marked synthetic',
$authored
);

if ( 0 < $failures ) {
fwrite(STDERR, "synthetic image figure inline flow FAILED: {$passes} passed, {$failures} failed\n");
exit(1);
}
echo "synthetic image figure inline flow passed: {$passes} assertions\n";
Loading