compute_layout = function(node, x,y, nodes_to_render, font)
	-- append to nodes_to_render flattened instructions to render a hierarchy of nodes
	-- return x,y rendered until (surface coordinates)
	if node.type == 'text' then
		-- leaf node containing raw text
		node.x = x
		node.y = y
		-- render background if necessary
		local pending_nodes = {}
		if node.bg then
			local n = {type='rectangle', drawmode='fill', r=node.bg.r, g=node.bg.g, b=node.bg.b, x=node.x, y=node.y, rx=node.rx, ry=node.ry, text_node=node}
			table.insert(pending_nodes, n)
			table.insert(nodes_to_render, n)
		end
		-- render border if necessary
		if node.border then
			local n = {type='rectangle', drawmode='line', r=node.border.r, g=node.border.g, b=node.border.b, x=node.x, y=node.y, rx=node.rx, ry=node.ry}
			table.insert(pending_nodes, n)
			table.insert(nodes_to_render, n)
		end
		-- render contents
		if node.width then
			node.w = node.width
		else
			node.w = 0
			for i,s in ipairs(node.data) do
				local width = love.graphics.getFont():getWidth(s)/Viewport.zoom
				if node.w < width then node.w = width end
			end
		end
		if node.editor == nil then
			initialize_editor(node)
		else
			update_editor_box(node, font)
		end
		node.h = box_height(node)
		table.insert(nodes_to_render, node)
		for _,n in ipairs(pending_nodes) do
			n.w = node.w
			n.h = node.h
		end
	elseif node.type == 'rows' then
		node.x = x
		node.y = y
		local node_to_render
		if node.bg then
			node_to_render = {type='rectangle', r=node.bg.r, g=node.bg.g, b=node.bg.b, x=node.x, y=node.y, text_node=node}
			table.insert(nodes_to_render, node_to_render)
		end
		-- lay out children top to bottom
		local subx,suby = x,y
		local w,h = 0,0
		local subnodes
		for _,child in ipairs(node.data) do
			if child.margin then
				suby = suby+child.margin
				h = h+child.margin
			end
			if not child.width then
				child.width = node.width  -- HACK: should we set child.w or child.width? Neither is quite satisfactory.
			end
			subx,suby = compute_layout(child, x,suby, nodes_to_render, font)
			if w < child.w then
				w = child.w
			end
			h = h+child.h
		end
		node.w = w
		node.h = h
		if node_to_render then
			node_to_render.w = w
			node_to_render.h = h
		end
	elseif node.type == 'cols' then
		node.x = x
		node.y = y
		-- lay out children left to right
		local node_to_render
		if node.bg then
			node_to_render = {type='rectangle', r=node.bg.r, g=node.bg.g, b=node.bg.b, x=node.x, y=node.y, text_node=node}
			table.insert(nodes_to_render, node_to_render)
		end
		local subx,suby = x,y
		local w,h = 0,0
		for _,child in ipairs(node.data) do
			if child.margin then
				subx = subx+child.margin
				w = w+child.margin
			end
			subx,suby = compute_layout(child, subx,y, nodes_to_render, font)
			w = w + child.w
			if h < child.h then
				h = child.h
			end
		end
		node.w = w
		node.h = h
		if node_to_render then
			node_to_render.w = w
			node_to_render.h = h
		end
	end
	return x+node.w,y+node.h
end