Row storage layout
Your task
A storage engine writes each table row to disk in one fixed layout. The row opens with header_bytes of bookkeeping, then a null bitmap holding one bit per column rounded up to whole bytes, so nine columns cost two. Then the column values follow in declaration order. Each entry of columns is one column written as "name|width", where width is the bytes one value takes and is at least 1. A column of width 1, 2, 4 or 8 is a fixed width type and must begin at an offset that is a multiple of its width, counted from the start of the row, so the engine inserts padding ahead of it when the running offset is not already a multiple. A column of any other width is variable length text and needs no padding. After the last column the row is padded up to a multiple of 8. Return the total bytes the row occupies.
Examples
row_layout_bytes(columns: list[str], header_bytes: int) -> introw_layout_bytes(["id|8", "flag|1", "score|4", "name|20"], 4)48row_layout_bytes(["a|1", "b|1", "c|1"], 0)8row_layout_bytes(["blob|17", "n|8"], 0)32