LibWeb: Improve handling of min/max constraint violations on images

Instead of bailing after resolving one violated constraint, we have to
continue down the list of remaining constraints.

We now also call the constraint solver for all replaced elements with
"auto" for both width and height.

Co-authored-by: 0GreenClover0 <clovers02123@gmail.com>
This commit is contained in:
Andreas Kling 2023-05-09 09:56:30 +02:00
parent 90b21890c5
commit 197efc8985
Notes: sideshowbarker 2024-07-17 11:29:41 +09:00
3 changed files with 71 additions and 23 deletions

View File

@ -0,0 +1,6 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (1,1) content-size 798x80 [BFC] children: not-inline
BlockContainer <body> at (10,10) content-size 780x62 children: inline
line 0 width: 82, height: 62, bottom: 62, baseline: 62
frag 0 from ImageBox start: 0, length: 0, rect: [11,11 80x60]
ImageBox <img> at (11,11) content-size 80x60 children: not-inline

View File

@ -0,0 +1,9 @@
<!doctype html><style>
* { border: 1px solid black; }
body { background: #444; }
img {
max-height: 60px;
height: auto;
max-width: 100%;
}
</style><img src="data:image/gif;base64,R0lGODdhgALgAYAAAP///////ywAAAAAgALgAQAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImKi4yNjo+AgZKTlJWWl5iZmpucnZ6fkJGio6SlpqeoqaqrrK2ur6ChsrO0tba3uLm6u7y9vr+wscLDxMXGx8jJysvMzc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f7/8PMKDAgQQLGjyIMKHChQwbOnwIMaLEiRQrWryIMaPG/o0cO3r8CDKkyJEkS5o8iTKlypUsW7p8CTOmzJk0a9q8iTOnzp08e/r8CTSo0KFEixo9ijSp0qVMmzp9CjWq1KlUq1q9ijWr1q1cu3r9Cjas2LFky5o9izat2rVs27p9Czeu3Ll069q9izev3r18+/r9Cziw4MGECxs+jDix4sWMGzt+DDmy5MmUK1u+jDmz5s2cO3v+DDq06NGkS5s+jTq16tWsW7t+DTu27Nm0a9u+jTu37t28e/v+DTy48OHEixs/jjy58uXMmzt/Dj269OnUq1u/jj279u3cu3v/Dj68+PHky5s/jz69+vXs27t/Dz++/Pn069u/jz+//v38/vv7/w9ggAIOSGCBBh6IYIIKLshggw4+CGGEEk5IYYUWXohhhhpuyGGHHn4IYogijkhiiSaeiGKKKq7IYosuvghjjDLOSGONNt6IY4467shjjz7+CGSQQg5JZJFGHolkkkouyWSTTj4JZZRSTklllVZeiWWWWm7JZZdefglmmGKOSWaZZp6JZppqrslmm26+CWeccs5JZ5123olnnnruyWeffv4JaKCCDkpooYYeimiiii7KaKOOPgpppJJOSmmlll6Kaaaabsppp55+Cmqooo5Kaqmmnopqqqquymqrrr4Ka6yyzkprrbbeimuuuu7Ka6++/gpssMIOS2yxxh6LYWyyyi7LbLPOPgtttNJOS2211l6Lbbbabsttt95+C2644o5Lbrnmnotuuuquy2677r4Lb7zyzktvvfbei2+++u7Lb7/+/gtwwAIPTHDBBh+McMIKL8xwww4/DHHEEk/MUAEAOw==" />

View File

@ -258,7 +258,7 @@ FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_
};
}
static CSSPixelSize solve_replaced_size_constraint(LayoutState const& state, CSSPixels w, CSSPixels h, ReplacedBox const& box)
static CSSPixelSize solve_replaced_size_constraint(LayoutState const& state, CSSPixels input_width, CSSPixels input_height, ReplacedBox const& box)
{
// 10.4 Minimum and maximum widths: 'min-width' and 'max-width'
@ -268,35 +268,42 @@ static CSSPixelSize solve_replaced_size_constraint(LayoutState const& state, CSS
auto height_of_containing_block = containing_block_state.content_height();
CSSPixels specified_min_width = box.computed_values().min_width().is_auto() ? 0 : box.computed_values().min_width().to_px(box, width_of_containing_block);
CSSPixels specified_max_width = box.computed_values().max_width().is_none() ? w : box.computed_values().max_width().to_px(box, width_of_containing_block);
CSSPixels specified_max_width = box.computed_values().max_width().is_none() ? input_width : box.computed_values().max_width().to_px(box, width_of_containing_block);
CSSPixels specified_min_height = box.computed_values().min_height().is_auto() ? 0 : box.computed_values().min_height().to_px(box, height_of_containing_block);
CSSPixels specified_max_height = box.computed_values().max_height().is_none() ? h : box.computed_values().max_height().to_px(box, height_of_containing_block);
CSSPixels specified_max_height = box.computed_values().max_height().is_none() ? input_height : box.computed_values().max_height().to_px(box, height_of_containing_block);
auto min_width = min(specified_min_width, specified_max_width);
auto max_width = max(specified_min_width, specified_max_width);
auto min_height = min(specified_min_height, specified_max_height);
auto max_height = max(specified_min_height, specified_max_height);
struct Size {
CSSPixels width;
CSSPixels height;
} size = { input_width, input_height };
auto& w = size.width;
auto& h = size.height;
if (w > max_width)
return { w, max(max_width * (h / w), min_height) };
size = { max_width, max(max_width * (h / w), min_height) };
if (w < min_width)
return { max_width, min(min_width * (h / w), max_height) };
size = { min_width, min(min_width * (h / w), max_height) };
if (h > max_height)
return { max(max_height * (w / h), min_width), max_height };
size = { max(max_height * (w / h), min_width), max_height };
if (h < min_height)
return { min(min_height * (w / h), max_width), min_height };
if ((w > max_width && h > max_height) && (max_width / w < max_height / h))
return { max_width, max(min_height, max_width * (h / w)) };
size = { min(min_height * (w / h), max_width), min_height };
if ((w > max_width && h > max_height) && (max_width / w <= max_height / h))
size = { max_width, max(min_height, max_width * (h / w)) };
if ((w > max_width && h > max_height) && (max_width / w > max_height / h))
return { max(min_width, max_height * (w / h)), max_height };
if ((w < min_width && h < min_height) && (min_width / w < min_height / h))
return { min(max_width, min_height * (w / h)), min_height };
size = { max(min_width, max_height * (w / h)), max_height };
if ((w < min_width && h < min_height) && (min_width / w <= min_height / h))
size = { min(max_width, min_height * (w / h)), min_height };
if ((w < min_width && h < min_height) && (min_width / w > min_height / h))
return { min_width, min(max_height, min_width * (h / w)) };
size = { min_width, min(max_height, min_width * (h / w)) };
if (w < min_width && h > max_height)
return { min_width, max_height };
size = { min_width, max_height };
if (w > max_width && h < min_height)
return { max_width, min_height };
size = { max_width, min_height };
return { w, h };
}
@ -430,10 +437,18 @@ CSSPixels FormattingContext::compute_width_for_replaced_element(LayoutState cons
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto computed_width = should_treat_width_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().width();
auto computed_height = should_treat_height_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().height();
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
auto used_width = tentative_width_for_replaced_element(state, box, computed_width, available_space);
if (computed_width.is_auto() && computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) {
CSSPixels w = used_width;
CSSPixels h = tentative_height_for_replaced_element(state, box, computed_height, available_space);
used_width = solve_replaced_size_constraint(state, w, h, box).width();
return used_width;
}
// 2. The tentative used width is greater than 'max-width', the rules above are applied again,
// but this time using the computed value of 'max-width' as the computed value for 'width'.
auto computed_max_width = box.computed_values().max_width();
@ -485,28 +500,46 @@ CSSPixels FormattingContext::tentative_height_for_replaced_element(LayoutState c
CSSPixels FormattingContext::compute_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space)
{
// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
// 'inline-block' replaced elements in normal flow and floating replaced elements
// 10.6.2 Inline replaced elements
// 10.6.4 Block-level replaced elements in normal flow
// 10.6.6 Floating replaced elements
// 10.6.10 'inline-block' replaced elements in normal flow
auto height_of_containing_block = state.get(*box.containing_block()).content_height();
auto computed_width = should_treat_width_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().width();
auto computed_height = should_treat_height_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().height();
// 1. The tentative used height is calculated (without 'min-height' and 'max-height')
CSSPixels used_height = tentative_height_for_replaced_element(state, box, computed_height, available_space);
// However, for replaced elements with both 'width' and 'height' computed as 'auto',
// use the algorithm under 'Minimum and maximum widths'
// https://www.w3.org/TR/CSS22/visudet.html#min-max-widths
// to find the used width and height.
if (computed_width.is_auto() && computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) {
CSSPixels w = tentative_width_for_replaced_element(state, box, computed_width, available_space);
CSSPixels h = used_height;
used_height = solve_replaced_size_constraint(state, w, h, box).height();
return used_height;
}
auto const& computed_min_height = box.computed_values().min_height();
auto const& computed_max_height = box.computed_values().max_height();
// 2. If this tentative height is greater than 'max-height', the rules above are applied again,
// but this time using the value of 'max-height' as the computed value for 'height'.
auto computed_max_height = box.computed_values().max_height();
if (!computed_max_height.is_none()) {
if (used_height > computed_max_height.to_px(box, height_of_containing_block)) {
used_height = tentative_height_for_replaced_element(state, box, computed_max_height, available_space);
}
}
if (!computed_max_height.is_none())
used_height = min(used_height, computed_max_height.to_px(box, height_of_containing_block));
if (!computed_min_height.is_auto())
used_height = max(used_height, computed_min_height.to_px(box, height_of_containing_block));
// 3. If the resulting height is smaller than 'min-height', the rules above are applied again,
// but this time using the value of 'min-height' as the computed value for 'height'.
auto computed_min_height = box.computed_values().min_height();
if (!computed_min_height.is_auto()) {
if (used_height < computed_min_height.to_px(box, height_of_containing_block)) {
used_height = tentative_height_for_replaced_element(state, box, computed_min_height, available_space);
}
}
return used_height;
}