So far, layout has been a linear process that handles open tags and and close tags independently. But web pages are trees, and look like them: borders and backgrounds visually nest inside one another. To support that, this chapter switches to tree-based layout, where the tree of elements is transformed into a tree of layout objects for the visual elements of the page. In the process, we’ll make our web pages more colorful with backgrounds.
Right now, our browser lays out an element’s open and close tags separately. Both tags modify global state, like the cursor_x
and cursor_y
variables, but they aren’t otherwise connected, and information about the element as a whole, like its width and height, is never computed. That makes it pretty hard to draw a background color behind text. So web browsers structure layout differently.
In a browser, layout is about producing a layout tree, whose nodes are layout objects, each associated with an HTML element,Elements like <script>
don’t generate layout objects, and some elements generate multiple (<li>
elements have a layout object for the bullet point!), but mostly it’s one layout object each. and each with a size and a position. The browser walks the HTML tree to produce the layout tree, then computes the size and position for each layout object, and finally draws each layout object to the screen.
Let’s start a new class called BlockLayout
, which will represent a node in the layout tree. Like our Element
class, layout objects form a tree, so they have a list of children
and a parent
. We’ll also have a node
field for the HTML element the layout object corresponds to.
class BlockLayout:
def __init__(self, node, parent, previous):
self.node = node
self.parent = parent
self.previous = previous
self.children = []
I’ve also added a field for the layout object’s previous sibling. We’ll need it to compute sizes and positions.
Each layout object also needs a size and position, which we’ll store in the width
, height
, x
, and y
fields. But let’s leave that for later. The first job for BlockLayout
is creating the layout tree itself.
We’ll do that in a new layout
method, looping over each child node and creating a new child layout object for it.
class BlockLayout:
def layout(self):
previous = None
for child in self.node.children:
next = BlockLayout(child, self, previous)
self.children.append(next)
previous = next
This code is tricky because it involves two trees. The node
and child
are part of the HTML tree; but self
, previous
, and next
are part of the layout tree. The two trees have similar structure, so it’s easy to get confused. But remember that this code constructs the layout tree from the HTML tree. So it reads from node.children
(in the HTML tree) and writes to self.children
(in the layout tree).
So this creates layout objects for the direct children of the node in question. Now those children’s own layout
methods can be called to build the whole tree recursively:
def layout(self):
# ...
for child in self.children:
child.layout()
We’ll discuss the base case of the recursion in just a moment, but first let’s ask how it starts. Inconveniently, the BlockLayout
constructor requires a parent node, so we need another kind of layout object at the root.You couldn’t just use None
for the parent, because the root layout object also computes its size and position differently, as we’ll see later this chapter. I think of that root as the document itself, so let’s call it DocumentLayout
:
So we’re building a layout tree with one layout object per HTML node, plus an extra layout object at the root, by recursively calling layout
. It looks like this:
In this example there are four BlockLayout
objects, in green, one per element. There’s also a DocumentLayout
at the root.
The browser must now move on to computing sizes and positions for each layout object. But before we write that code, we have to face an important truth: different HTML elements are laid out differently. They need different kinds of layout objects!
Elements like <body>
and <header>
contain blocks stacked vertically. But elements like <p>
and <h1>
contain text and lay that text out horizontally in lines.In European languages, at least! Abstracting a bit, there are two layout modes, two ways an element can be laid out relative to its children: block layout and inline layout.
We’ve already got BlockLayout
for block layout. And actually, we’ve already got inline layout too: it’s just the text layout we’ve been implementing since Chapter 2. So let’s rename the existing Layout
class to InlineLayout
and refactor to match methods with BlockLayout
.
Rename Layout
to InlineLayout
and rename its constructor to layout
. Add a new constructor similar to BlockLayout
’s:
In the new layout
method, replace the tree
argument with the node
field: