Repository: Kozea/WeasyPrint

Issue:
Wrong vertical alignment of float elements in tables
There seems to be an issue with calculating table width even after #2087 
The results differ between WeasyPrint and how browsers render the tables.

```html
<!DOCTYPE html><html><head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
            
        

</head>
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0" style="width: 100%;-webkit-text-size-adjust: none;margin: 0;padding: 0;color: #000;font-family: Verdana,sans-serif;font-size: 11pt;line-height: normal;text-align: left">
    <div style="font-family: Verdana,sans-serif; font-size: 11pt; min-height: 1em; text-align: justify">
<table border="0" style="height: 67px; width: 100%; background-color: #ffffff; margin-left: auto; margin-right: auto">
<tbody style="">
<tr style="height: 33px">
<td style="padding: 0; border-collapse: collapse; width: 798px; height: 33px;">
<table border="0" style="height: 168px; width: 100%; border-collapse: collapse; background-color: #cad7e0; border-style: none">
<tbody style="">
<tr style="height: 168px">
<td style="padding: 0; border-collapse: collapse; width: 100%; height: 168px; border-style: none;">
<table border="0" style="height: 168px; width: 362px; border-collapse: collapse; background-color: #cad7e0; border-style: none; float: right">
<tbody style="">
<tr style="">
<td style="padding: 0; border-collapse: collapse; width: 362px; border-style: none;"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr style="height: 34px">
<td style="padding: 0; border-collapse: collapse; width: 640px; height: 34px;">
<div style="font-family: Verdana,sans-serif; font-size: 11pt; text-align: left; min-height: 1em">&nbsp;</div>
<table border="0" height="22" style="height: 14px; width: 90%; border-collapse: collapse; border-style: none; margin-left: auto; margin-right: auto">
<tbody style="">
<tr style="height: 18px">
<td style="padding: 0; border-collapse: collapse; width: 600px; height: 18px; border-style: none;"><div style="min-height: 1em">test</div></td>
</tr>
</tbody>
</table>
<div style="min-height: 1em">&nbsp;</div>
</td>
</tr>
</tbody>
</table>
</div>


</body></html>
```

WeasyPrint:
![image](https://github.com/user-attachments/assets/a7f1bfc4-5541-4b94-91fd-57d534ef99ff)

Chrome:
![image](https://github.com/user-attachments/assets/4cc323ab-c70e-4a85-91d9-72ab917f6bb5)

The html is dynamic it comes from tinymce editor so we don't have 100% control over it.

Discussion:
@liZe: It’s not a problem with the columns widths, it’s because of the float in a row with fixed height.

```html
<table style="width: 200px; background-color: green">
  <tr>
    <td style="height: 100px">
      <div style="height: 100px; width: 100px; background-color: blue; float: right"></div>
    </td>
  </tr>
</table>
```@liZe: Thanks for the report! It should work now, feedback is welcome.

Solution patch:
diff --git a/docs/first_steps.rst b/docs/first_steps.rst
index 643365df4..0028ad75b 100644
--- a/docs/first_steps.rst
+++ b/docs/first_steps.rst
@@ -77,9 +77,16 @@ To install WeasyPrint using your distribution’s package::
 
   apk add weasyprint
 
-To install WeasyPrint, you need the following packages::
+To install WeasyPrint inside a virtualenv using wheels (if possible), you need
+the following packages::
+
+  apk add py3-pip so:libgobject-2.0.so.0 so:libpango-1.0.so.0 so:libharfbuzz.so.0 so:libharfbuzz-subset.so.0 so:libfontconfig.so.1 so:libpangoft2-1.0.so.0
+
+To install WeasyPrint inside a virtualenv without using wheels, you need the
+following packages::
 
-  apk add py3-pip gcc musl-dev python3-dev pango zlib-dev jpeg-dev openjpeg-dev g++ libffi-dev harfbuzz-subset
+  apk add py3-pip so:libgobject-2.0.so.0 so:libpango-1.0.so.0 so:libharfbuzz.so.0 so:libharfbuzz-subset.so.0 so:libfontconfig.so.1 so:libpangoft2-1.0.so.0
+  apk add gcc musl-dev python3-dev zlib-dev jpeg-dev openjpeg-dev libwebp-dev g++ libffi-dev
 
 
 Archlinux
diff --git a/weasyprint/layout/table.py b/weasyprint/layout/table.py
index 2a8992c83..d08d54ed2 100644
--- a/weasyprint/layout/table.py
+++ b/weasyprint/layout/table.py
@@ -144,9 +144,6 @@ def group_layout(group, position_y, bottom_space, page_is_empty, skip_stack):
                     sum(spanned_widths) +
                     border_spacing_x * (cell.colspan - 1) -
                     borders_plus_padding)
-                # Set computed height as a minimum.
-                cell.computed_height = cell.height
-                cell.height = 'auto'
                 if skip_stack:
                     if index_cell in skip_stack:
                         cell_skip_stack = skip_stack[index_cell]
@@ -175,11 +172,20 @@ def group_layout(group, position_y, bottom_space, page_is_empty, skip_stack):
                 # force to render something if the page is actually empty, or
                 # just draw an empty cell otherwise. See
                 # test_table_break_children_margin.
+                # Pretend that height is not set, keeping computed height as a minimum.
+                cell.computed_height = cell.height
+                cell.height = 'auto'
+                original_style = cell.style
+                if cell.style['height'] != 'auto':
+                    style_copy = cell.style.copy()
+                    style_copy['height'] = 'auto'
+                    cell.style = style_copy
                 new_cell, cell_resume_at, _, _, _, _ = block_container_layout(
                     context, cell, bottom_space, cell_skip_stack,
                     page_is_empty=page_is_empty, absolute_boxes=absolute_boxes,
                     fixed_boxes=fixed_boxes, adjoining_margins=None,
                     discard=False, max_lines=None)
+                cell.style = original_style
                 if new_cell is None:
                     cell = cell.copy_with_children([])
                     cell, _, _, _, _, _ = block_container_layout(
@@ -289,10 +295,7 @@ def group_layout(group, position_y, bottom_space, page_is_empty, skip_stack):
                             cell.computed_height - cell.content_height)
                     if vertical_align_shift > 0:
                         for child in cell.children:
-                            child_shift = child.margin_height()
-                            if cell.vertical_align == 'middle':
-                                child_shift /= 2
-                            child.translate(dy=vertical_align_shift - child_shift)
+                            child.translate(dy=vertical_align_shift)
 
             next_position_y = row.position_y + row.height
             if resume_at is None:


Test patch:
diff --git a/tests/layout/test_table.py b/tests/layout/test_table.py
index 270d4b4e1..06044033e 100644
--- a/tests/layout/test_table.py
+++ b/tests/layout/test_table.py
@@ -1966,17 +1966,50 @@ def test_table_vertical_align(assert_pixels):
 
 @assert_no_logs
 def test_table_vertical_align_float():
-    # Test regression: https://github.com/Kozea/WeasyPrint/issues/2216
+    # Test regressions:
+    # https://github.com/Kozea/WeasyPrint/issues/2216
+    # https://github.com/Kozea/WeasyPrint/issues/2293
     page, = render_pages('''
       <style>
-        @page { size: 100px }
-        td { width: 50px; height: 100px }
-        div { width: 25px; height: 20px }
+        @page { size: 100px 400px }
+        td { width: 50px; height: 100px; line-height: 0 }
       </style>
       <table>
         <tr>
-          <td style="vertical-align: middle"><div style="float: left"></div></td>
-          <td style="vertical-align: bottom"><div style="float: right"></div></td>
+          <td style="vertical-align: middle">
+            <div style="float: left; height: 20px; width: 20px"></div>
+            <div style="display: inline-block; height: 30px; width: 30px"></div>
+          </td>
+          <td style="vertical-align: bottom">
+            <div style="display: inline-block; height: 30px; width: 30px"></div>
+            <div style="float: right; height: 20px; width: 20px"></div>
+          </td>
+        </tr>
+        <tr>
+          <td style="vertical-align: middle">
+            <div style="float: left; height: 20px; width: 20px"></div>
+            <div style="display: inline-block; height: 10px; width: 10px"></div>
+          </td>
+          <td style="vertical-align: bottom">
+            <div style="display: inline-block; height: 10px; width: 10px"></div>
+            <div style="float: right; height: 20px; width: 20px"></div>
+          </td>
+        </tr>
+        <tr>
+          <td style="vertical-align: middle">
+            <div style="display: inline-block; height: 10px; width: 10px"></div>
+          </td>
+          <td style="vertical-align: bottom">
+            <div style="display: inline-block; height: 10px; width: 10px"></div>
+          </td>
+        </tr>
+        <tr>
+          <td style="vertical-align: middle">
+            <div style="float: left; height: 20px; width: 20px"></div>
+          </td>
+          <td style="vertical-align: bottom">
+            <div style="float: right; height: 20px; width: 20px"></div>
+          </td>
         </tr>
       </table>
     ''')
@@ -1986,14 +2019,52 @@ def test_table_vertical_align_float():
     table, = wrapper.children
     table, = wrapper.children
     row_group, = table.children
-    row, = row_group.children
-    td_1, td_2 = row.children
+    row_1, row_2, row_3, row_4, = row_group.children
+
+    td_1, td_2 = row_1.children
+    div_1, line_wrapper = td_1.children
+    assert div_1.position_x == 0
+    assert div_1.position_y == (100 - 30) / 2
+    div_2 = line_wrapper.children[0].children[0]
+    assert div_2.position_x == 20
+    assert div_2.position_y == (100 - 30) / 2
+    line_box, = td_2.children
+    div_1, _, div_2 = line_box.children
+    assert div_1.position_x == 50
+    assert div_1.position_y == 100 - 30
+    assert div_2.position_x == 80
+    assert div_2.position_y == 100 - 30
+
+    td_1, td_2 = row_2.children
+    div_1, line_wrapper = td_1.children
+    assert div_1.position_x == 0
+    assert div_1.position_y == 100 + (100 - 20) / 2
+    div_2 = line_wrapper.children[0].children[0]
+    assert div_2.position_x == 20
+    assert div_2.position_y == 100 + (100 - 20) / 2
+    line_box, = td_2.children
+    div_1, _, div_2 = line_box.children
+    assert div_1.position_x == 50
+    assert div_1.position_y == 100 + (100 - 20)
+    assert div_2.position_x == 80
+    assert div_2.position_y == 100 + (100 - 20)
+
+    td_1, td_2 = row_3.children
+    line_box, = td_1.children
+    div, _ = line_box.children
+    assert div.position_x == 0
+    assert div.position_y == 200 + (100 - 10) / 2
+    div, = td_2.children
+    assert div.position_x == 50
+    assert div.position_y == 200 + (100 - 10)
+
+    td_1, td_2 = row_4.children
     div, = td_1.children
     assert div.position_x == 0
-    assert div.position_y == 40  # (100 - 20) / 2
+    assert div.position_y == 300 + (100 - 20) / 2
     div, = td_2.children
-    assert div.position_x == 75
-    assert div.position_y == 80  # 100 - 20
+    assert div.position_x == 80
+    assert div.position_y == 300 + (100 - 20)
 
 
 @assert_no_logs