def collapse_table_borders(table, grid_width, grid_height):
    """Resolve border conflicts for a table in the collapsing border model.

    Take a :class:`TableBox`; set appropriate border widths on the table,
    column group, column, row group, row, and cell boxes; and return
    a data structure for the resolved collapsed border grid.

    """
    if not (grid_width and grid_height):
        # Don’t bother with empty tables.
        return [], []

    styles = reversed([
        'hidden', 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset',
        'groove', 'inset', 'none'])
    style_scores = {style: score for score, style in enumerate(styles)}
    style_map = {'inset': 'ridge', 'outset': 'groove'}
    weak_null_border = ((0, 0, style_scores['none']), ('none', 0, TRANSPARENT))

    # Borders are always stored left to right, top to bottom.
    vertical_borders = [
        [weak_null_border] * (grid_width + 1) for _ in range(grid_height)]
    horizontal_borders = [
        [weak_null_border] * grid_width for _ in range(grid_height + 1)]

    def set_one_border(border_grid, box_style, side, grid_x, grid_y):
        from ..draw.color import get_color

        style = box_style[f'border_{side}_style']
        width = box_style[f'border_{side}_width']
        color = get_color(box_style, f'border_{side}_color')

        # See https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution.
        score = ((1 if style == 'hidden' else 0), width, style_scores[style])

        style = style_map.get(style, style)
        previous_score, _ = border_grid[grid_y][grid_x]
        # Strict < so that the earlier call wins in case of a tie.
        if previous_score < score:
            border_grid[grid_y][grid_x] = (score, (style, width, color))

    def set_borders(box, x, y, w, h):
        style = box.style

        # x and y are logical (possibly rtl), but borders are graphical (always ltr).
        if table.style['direction'] == 'ltr':
            for yy in range(y, y + h):
                set_one_border(vertical_borders, style, 'left', x, yy)
                set_one_border(vertical_borders, style, 'right', x + w, yy)
            for xx in range(x, x + w):
                set_one_border(horizontal_borders, style, 'top', xx, y)
                set_one_border(horizontal_borders, style, 'bottom', xx, y + h)
        else:
            for yy in range(y, y + h):
                set_one_border(vertical_borders, style, 'left', -1 - w - x, yy)
                set_one_border(vertical_borders, style, 'right', -1 - x, yy)
            for xx in range(-1 - x, -1 - x - w, -1):
                set_one_border(horizontal_borders, style, 'top', xx, y)
                set_one_border(horizontal_borders, style, 'bottom', xx, y + h)

    # Set cell borders. The order is important here:
    # "A style set on a cell wins over one on a row, which wins over a
    #  row group, column, column group and, lastly, table"
    # See https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution.
    strong_null_border = ((1, 0, style_scores['hidden']), ('hidden', 0, TRANSPARENT))
    grid_y = 0
    for row_group in table.children:
        for row in row_group.children:
            for cell in row.children:
                # Force null border inside of a cell with rowspan or colspan.
                grid_x, colspan, rowspan = cell.grid_x, cell.colspan, cell.rowspan
                if table.style['direction'] == 'ltr':
                    vertical_x_range = range(grid_x + 1, grid_x + colspan)
                    horizontal_x_range = range(grid_x, grid_x + colspan)
                else:
                    vertical_x_range = range(-2 - grid_x, -1 - grid_x - colspan, -1)
                    horizontal_x_range = range(-1 - grid_x, -1 - grid_x - colspan, -1)
                for xx in vertical_x_range:
                    for yy in range(grid_y, grid_y + rowspan):
                        vertical_borders[yy][xx] = strong_null_border
                for xx in horizontal_x_range:
                    for yy in range(grid_y + 1, grid_y + rowspan):
                        horizontal_borders[yy][xx] = strong_null_border
                # Set cell border.
                set_borders(cell, grid_x, grid_y, colspan, rowspan)
            grid_y += 1

    # Set row borders.
    grid_y = 0
    for row_group in table.children:
        for row in row_group.children:
            set_borders(row, 0, grid_y, grid_width, 1)
            grid_y += 1

    # Set row group borders.
    grid_y = 0
    for row_group in table.children:
        rowspan = len(row_group.children)
        set_borders(row_group, 0, grid_y, grid_width, rowspan)
        grid_y += rowspan

    # Set column borders.
    for column_group in table.column_groups:
        for column in column_group.children:
            set_borders(column, column.grid_x, 0, 1, grid_height)

    # Set column group group borders.
    for column_group in table.column_groups:
        set_borders(
            column_group, column_group.grid_x, 0, column_group.span, grid_height)

    # Set table borders.
    set_borders(table, 0, 0, grid_width, grid_height)

    # Now that all conflicts are resolved, set transparent borders of the
    # correct widths on each box. The actual border grid will be painted
    # separately.
    def set_border_used_width(box, side, twice_width):
        prop = f'border_{side}_width'
        setattr(box, prop, twice_width / 2)

    def remove_borders(box):
        set_border_used_width(box, 'top', 0)
        set_border_used_width(box, 'right', 0)
        set_border_used_width(box, 'bottom', 0)
        set_border_used_width(box, 'left', 0)

    def max_vertical_width(x, y1, y2):
        return max(grid_row[x][1][1] for grid_row in vertical_borders[y1:y2])

    def max_horizontal_width(x1, y, x2):
        return max(width for _, (_, width, _) in horizontal_borders[y][x1:x2])

    grid_y = 0
    for row_group in table.children:
        remove_borders(row_group)
        for row in row_group.children:
            remove_borders(row)
            for cell in row.children:
                x, y = cell.grid_x, grid_y
                colspan, rowspan = cell.colspan, cell.rowspan
                if table.style['direction'] == 'ltr':
                    top = max_horizontal_width(x, y, x + colspan)
                    bottom = max_horizontal_width(x, y + rowspan, x + colspan)
                    left = max_vertical_width(x, y, y + rowspan)
                    right = max_vertical_width(x + colspan, y, y + rowspan)
                else:
                    top = max_horizontal_width(-colspan - x, y, -x or None)
                    bottom = max_horizontal_width(-colspan - x, y + rowspan, -x or None)
                    left = max_vertical_width(-1 - colspan - x, y, y + rowspan)
                    right = max_vertical_width(-1 - x, y, y + rowspan)
                set_border_used_width(cell, 'top', top)
                set_border_used_width(cell, 'bottom', bottom)
                set_border_used_width(cell, 'left', left)
                set_border_used_width(cell, 'right', right)
            grid_y += 1

    for column_group in table.column_groups:
        remove_borders(column_group)
        for column in column_group.children:
            remove_borders(column)

    set_border_used_width(table, 'top', max_horizontal_width(0, 0, grid_width))
    set_border_used_width(
        table, 'bottom', max_horizontal_width(0, grid_height, grid_width))
    # "UAs must compute an initial left and right border width for the table
    #  by examining the first and last cells in the first row of the table."
    # https://www.w3.org/TR/CSS21/tables.html#collapsing-borders
    # ... so h=1, not grid_height:
    set_border_used_width(table, 'left', max_vertical_width(0, 0, 1))
    set_border_used_width(table, 'right', max_vertical_width(grid_width, 0, 1))

    return vertical_borders, horizontal_borders
Resolve border conflicts for a table in the collapsing border model.

Take a :class:`TableBox`; set appropriate border widths on the table,
column group, column, row group, row, and cell boxes; and return
a data structure for the resolved collapsed border grid.
weasyprint/layout/table.py