HTML TADS Table Layout Rules

The <TABLE> tag is probably the most powerful feature in HTML for controlling visual layout. The HTML specifications are intentionally vague about table layout, because the designers of HTML wanted to leave a lot of the details up to each implementation for maximum flexibility. HTML TADS is essentially an individual HTML implementation, though, so we can fill in some of the details that the generic HTML specifications leave out. Understanding the table layout rules can be helpful when trying to achieve particular effects.

The layout rules are fairly complicated, so we'll start with a simplified summary. To lay out a table, we start by finding the width of the table, then we find the width of its columns, then we let the chosen width determine the table's height. To find the width of the table and its columns, we try to satisfy any WIDTH attributes exactly if possible, giving precedence to the WIDTH attribute of the table itself if present. However, we never allow a table to be smaller than its contents, and we try to keep a table from exceeding the viewable width of the window, to avoid horizontal scrolling.

Now we'll describe the layout rules in detail.

Minimum and maximum column widths. Many of the layout rules involve the "minimum" and "maximum" width of a column. This is terminology from the HTML standard, so we'll use the same terms to describe the HTML TADS rules - but note that they're not always what you'd expect, so we'd better define them before now, before we get to the layout rules.

The minimum width of a column is the narrowest the column can be to fit all of its contents with the harshest possible word-wrapping; so, it's the width of the widest single word (or picture or other element) that can't be split across lines with word-wrapping.

The maximum width of a column is the largest of the maximum widths of the cells in the column. The maximum width of a cell is simply the total width of all of the contents of the cell with no word-wrapping; so it's the width of the cell with all of its contents on a single line. Note that this term can be a little misleading, because it isn't a maximum in the sense of a limit on how big the cell can get; rather, it's just a measurement of the most space a cell would need to display all of its contents without any word-wrapping. A cell (or a column) can always be made arbitrarily wider than this "maximum" by padding it with blank space.

Column WIDTH specifications. Some of the rules involve columns with explicitly specified widths. Column widths are specified using the WIDTH attribute of the <TD> and <TH> tags. When a column contains a <TD> or <TH> tag with a WIDTH attribute, then that column is given that specified width. If a single column contains multiple cells with WIDTH specifications, we only pay attention to the widest one.

A column width can be specified in pixels or as a percentage of the table's overall width. If the WIDTH value ends with a "%" sign, then it's a percentage width; otherwise, it's a pixel width.

When multiple columns specify percentage widths, we limit the total requested percentage to 100%; if the total exceeds 100%, we ignore the amount over 100%. For example, if there are two columns, and each specifies a width of 80%, we treat the second column as though it requested only 20%. Note that we don't apply the limit proportionally; we simply cut off the request for the first column that pushes the total over 100%.

Multi-column cells. If a cell has a COLSPAN greater than 1, we treat it specially. The simple summary: when a column spans multiple columns, we allocate its width to the columns it spans in proportion to the widths the columns would otherwise have based on the other cells in the columns.

To be more precise, let's look at the full algorithm.

First, we calculate the entire table's column widths for single-column cells (COLSPAN=1) only. Let's call these results the "current" column widths. We now make a second pass over the entire table, looking for multi-column cells.

For each multi-column cell, we calculate the cell's minimum and maximum widths as normal. We then add up the "current" minimum widths for the columns, and we add up the current maximum widths for the spanned columns; let's call these the "total current" minimum and maximum widths for the spanned columns. For example, if we have a cell that starts in column 2 and has COLSPAN=3, we add up the current minimum widths of columns 2, 3, and 4, and call this the total current minimum width; likewise for the total current maximum width.

Next, we compare the cell's minimum and maximum widths to the corresponding total current widths. If the cell's widths are smaller, we do nothing further, because the spanned columns in aggregate are big enough for the cell. If the cell's widths are bigger, we calculate the excess of the cell's widths over the corresponding total current widths. We then distribute this excess width to each spanned column in proportion to the column's share of the total current width. For example, if the current minimum widths of columns 2, 3, and 4 are 100, 200, and 300 respectively, and we have 60 pixels of excess width to distribute, we distribute one-sixth (100/(100+200+300)) to column 2, two-sixths to column 3, and three-sixths to column 4.

When we say we "distribute the excess widths," we simply mean that we increase the current column widths by the proportional share of the excess amount. So, after the example above, the new current minimum widths of columns 2, 3, and 4 become 110, 220, and 330.

Finally, we go back and repeat the process for the next multi-column cell we find in the table.

Figuring the table's width. The first step in laying out a table is determining the overall width of the table. The rules for choosing the width are:

Determining the column widths. Once we know the overall width of the table, we figure the widths of the individual columns. Since we choose the overall table width before determining the column widths, our job at this point is to determine how the table width is divided among the columns. We use the following rules, in order of priority:

Note that percentage width requests have higher priority than pixel widths. This means we'll satisfy any percentage requests (as closely as possible) before sastifying any pixel width requests; so, if there's not room to satisfy both kinds of requests, any pixel-width columns will be set to their minimum size to make as much room as possible for percentage-width columns.

When the table is so wide that there's extra space to distribute beyond explicit WIDTH requests, note that we preferentially distribute the space to unconstrained columns (columns without any WIDTH requests). We prefer to distribute space to unconstrained columns because this allows columns with explicit WIDTH requests to stay at the exact size requested.

Figuring the table's height. Once the column widths are chosen, the renderer performs word-wrapping on the contents of each cell to fit the contents to the final size of the cell's column. This process determines the minimum height of a cell, which is the height required to hold the word-wrapped contents of the cell at its final width. The minimum height of a row is the largest of the minimum heights of the row's cells.

If a cell or a row has a HEIGHT attribute, then we'll use the larger of the minimum height and the explicit HEIGHT attribute value.

If the <TABLE> tag has a HEIGHT attribute, and the height is larger than the sum of the minimum heights of the rows, then the table's height is the explicitly given HEIGHT; otherwise, the table's height is the sum of the minimum row heights. If the explicit HEIGHT is larger, then we distribute the extra vertical space (the difference between the explicit HEIGHT and the actual minimum height of the rows) among the rows as follows:

If a cell has a ROWSPAN greater than 1, we distribute its height among the rows it spans in proportion to the heights of the rows as they would be if we considered only the cells with ROWSPAN=1. The detailed algorithm is almost exactly the same as the one described above for multi-column (COLSPAN) cells.