Skip to content

Commit

Permalink
Take min/max-height into account in get_min_max_width for images
Browse files Browse the repository at this point in the history
This includes a rework of the size calculations for images, so that
min/max constraints are always honored. Previously, constraints were
ignored in some cases to maintain aspect ratio. See the last part of
the section on min/max widths in [1] for the spec on the matter.

TODO: While the containing block is not set yet on the frame during
min/max-width determination, it can already be determined in some cases
due to fixed dimensions on the ancestor forming the containing block.
In such cases, percentage values could be resolved already. This needs
more consideration, e.g. in regards to min/max dimensions on the parent
and percentages, which could necessitate checking further ancestors.
Maybe `get_min_max_width` could receive preliminary containing-block
dimensions, which would still be undefined if an `auto` width/height is
encountered.

[1] https://www.w3.org/TR/CSS21/visudet.html#min-max-widths

Fixes #2738
  • Loading branch information
Mellthas committed Jan 16, 2022
1 parent 1b49094 commit 946a618
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 187 deletions.
107 changes: 74 additions & 33 deletions src/FrameReflower/AbstractFrameReflower.php
Expand Up @@ -10,8 +10,9 @@
use Dompdf\Dompdf;
use Dompdf\Helpers;
use Dompdf\Frame;
use Dompdf\FrameDecorator\Block;
use Dompdf\Frame\Factory;
use Dompdf\FrameDecorator\AbstractFrameDecorator;
use Dompdf\FrameDecorator\Block;

/**
* Base reflower class
Expand All @@ -27,7 +28,7 @@ abstract class AbstractFrameReflower
/**
* Frame for this reflower
*
* @var Frame
* @var AbstractFrameDecorator
*/
protected $_frame;

Expand All @@ -47,9 +48,9 @@ abstract class AbstractFrameReflower

/**
* AbstractFrameReflower constructor.
* @param Frame $frame
* @param AbstractFrameDecorator $frame
*/
function __construct(Frame $frame)
function __construct(AbstractFrameDecorator $frame)
{
$this->_frame = $frame;
$this->_min_max_child_cache = null;
Expand Down Expand Up @@ -256,9 +257,9 @@ private function _get_collapsed_margin_length($length1, $length2)
* Handle relative positioning according to
* https://www.w3.org/TR/CSS21/visuren.html#relative-positioning.
*
* @param Frame $frame The frame to handle.
* @param AbstractFrameDecorator $frame The frame to handle.
*/
protected function position_relative(Frame $frame): void
protected function position_relative(AbstractFrameDecorator $frame): void
{
$style = $frame->get_style();

Expand Down Expand Up @@ -293,43 +294,83 @@ protected function position_relative(Frame $frame): void
abstract function reflow(Block $block = null);

/**
* Handle `min-width`/`max-width` properties while calculating the min/max
* width of this frame.
* Resolve the `min-width` property.
*
* Resolves to 0 if not set or if a percentage and the containing-block
* width is not defined.
*
* @param float $min Original min width.
* @param float $max Original max width.
* @param float|null $cbw Width of the containing block.
*
* @return array Restricted min and max width.
* @return float
*/
protected function restrict_min_max_width(float $min, float $max): array
protected function resolve_min_width(?float $cbw): float
{
// Ignore percentage values here, as the containing block is not
// defined yet
$style = $this->_frame->get_style();
$display = $style->display;

$min_width = $style->min_width;

return $min_width !== "auto" && $min_width !== "none"
? $style->length_in_pt($min_width, $cbw ?? 0)
: 0.0;
}

/**
* Resolve the `max-width` property.
*
* Resolves to `INF` if not set or if a percentage and the containing-block
* width is not defined.
*
* @param float|null $cbw Width of the containing block.
*
* @return float
*/
protected function resolve_max_width(?float $cbw): float
{
$style = $this->_frame->get_style();
$max_width = $style->max_width;
$has_min_width = $min_width !== "auto" && $min_width !== "none";
$has_max_width = $max_width !== "none" && $max_width !== "auto";

if ($has_max_width && !Helpers::is_percent($max_width)) {
$max = min($max, (float) $style->length_in_pt($max_width, 0));
$min = min($min, $max);
}
return $max_width !== "none" && $max_width !== "auto"
? $style->length_in_pt($max_width, $cbw ?? INF)
: INF;
}

if ($has_min_width && !Helpers::is_percent($min_width)) {
$min = max($min, (float) $style->length_in_pt($min_width, 0));
$max = max($max, $min);
} elseif ($has_max_width && Helpers::is_percent($max_width) && $display !== "table-cell") {
// Don't enforce any minimum width when max width depends on the
// containing block and there is no fixed min width
// Don't apply this logic to table cells, as percentage min/max
// columns widths are not supported
$min = 0.0;
}
/**
* Resolve the `min-height` property.
*
* Resolves to 0 if not set or if a percentage and the containing-block
* height is not defined.
*
* @param float|null $cbh Height of the containing block.
*
* @return float
*/
protected function resolve_min_height(?float $cbh): float
{
$style = $this->_frame->get_style();
$min_height = $style->min_height;

return $min_height !== "auto" && $min_height !== "none"
? $style->length_in_pt($min_height, $cbh ?? 0)
: 0.0;
}

/**
* Resolve the `max-height` property.
*
* Resolves to `INF` if not set or if a percentage and the containing-block
* height is not defined.
*
* @param float|null $cbh Height of the containing block.
*
* @return float
*/
protected function resolve_max_height(?float $cbh): float
{
$style = $this->_frame->get_style();
$max_height = $style->max_height;

return [$min, $max];
return $max_height !== "none" && $max_height !== "auto"
? $style->length_in_pt($style->max_height, $cbh ?? INF)
: INF;
}

/**
Expand Down
53 changes: 23 additions & 30 deletions src/FrameReflower/Block.php
Expand Up @@ -394,8 +394,8 @@ protected function _calculate_restricted_height()
}
}
} else {
// http://www.w3.org/TR/CSS21/visudet.html#normal-block
// http://www.w3.org/TR/CSS21/visudet.html#block-root-margin
// https://www.w3.org/TR/CSS21/visudet.html#normal-block
// https://www.w3.org/TR/CSS21/visudet.html#block-root-margin

if ($height === "auto") {
$height = $content_height;
Expand All @@ -407,33 +407,19 @@ protected function _calculate_restricted_height()
$margin_bottom = 0;
}

// FIXME: this should probably be moved to a separate function as per
// _calculate_restricted_width

$min_height = $style->min_height;
$max_height = $style->max_height;

if (isset($cb["h"])) {
$min_height = $style->length_in_pt($min_height, $cb["h"]);
$max_height = $style->length_in_pt($max_height, $cb["h"]);
} else {
$min_height = !Helpers::is_percent($min_height)
? $style->length_in_pt($min_height, $cb["w"])
: "auto";
$max_height = !Helpers::is_percent($max_height)
? $style->length_in_pt($max_height, $cb["w"])
: "none";
}

if ($max_height !== "none" && $max_height !== "auto" && $height > $max_height) {
$height = $max_height;
}

if ($min_height !== "auto" && $min_height !== "none" && $height < $min_height) {
$height = $min_height;
}
// Handle min/max height
// https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
$min_height = $this->resolve_min_height($cb["h"]);
$max_height = $this->resolve_max_height($cb["h"]);
$height = Helpers::clamp($height, $min_height, $max_height);
}

// TODO: Need to also take min/max height into account for absolute
// positioning, using similar logic to the `_calculate_width`/
// `calculate_restricted_width` split above. The non-absolute case
// can simply clamp height within min/max, as margins and offsets are
// not affected

return [$height, $margin_top, $margin_bottom, $top, $bottom];
}

Expand Down Expand Up @@ -935,8 +921,10 @@ function reflow(BlockFrameDecorator $block = null)

public function get_min_max_content_width(): array
{
// Ignore percentage values for a specified width here, as the
// containing block is not defined yet
// TODO: While the containing block is not set yet on the frame, it can
// already be determined in some cases due to fixed dimensions on the
// ancestor forming the containing block. In such cases, percentage
// values could be resolved here
$style = $this->_frame->get_style();
$width = $style->width;
$fixed_width = $width !== "auto" && !Helpers::is_percent($width);
Expand All @@ -951,6 +939,11 @@ public function get_min_max_content_width(): array
}

// Handle min/max width style properties
return $this->restrict_min_max_width($min, $max);
$min_width = $this->resolve_min_width(null);
$max_width = $this->resolve_max_width(null);
$min = Helpers::clamp($min, $min_width, $max_width);
$max = Helpers::clamp($max, $min_width, $max_width);

return [$min, $max];
}
}

0 comments on commit 946a618

Please sign in to comment.