Layout
A layout generator draws boxes laid out by CSS flexbox or grid around their text and shapes: a title card, a lower third, a dashboard, a panel of wrapped text. The engine lays it out with the fonts it draws with, at the size it draws at, so boxes fit their text in preview and export alike. This page is a condensed guide; the layout reference lists every field.
A layout is a generator
Section titled “A layout is a generator”A layout is written like any generator: as a generated source, a composition’s generator node, or a media node naming a layout source. Its picture is the canvas’s size, and a clip places, times and animates it.
{ "id": "lower-third", "kind": "generated", "generator": { "type": "layout", "root": { "children": [] } } }Every animatable value is still written in the animated form, { "mode": "constant", "value": ... } or keyframes.
Every node of the tree is a box. A box is a container when it has children, a leaf when it has text or shape, and empty otherwise (a spacer, a bar, a decoration).
| Group | Fields |
|---|---|
| Container | display (flex or grid), gap, row_gap, column_gap, justify_content, align_items, align_content |
| Flex container | flex_direction, flex_wrap |
| Grid container | grid_template_columns, grid_template_rows, grid_auto_columns, grid_auto_rows, grid_auto_flow, justify_items |
| Box | padding, background, border, border_radius, overflow |
| Item | width, height, min_*, max_*, aspect_ratio, margin, align_self, position, inset |
| Flex item | flex_grow, flex_shrink, flex_basis |
| Grid item | grid_column, grid_row, justify_self |
| Text style (inherited) | font_family, font_size, font_weight, fill, line_height, letter_spacing, text_align, strokes, shadows |
| Drawn after layout | opacity, translate, scale, rotation_degrees |
The root is the picture: its border box is the canvas, so it takes no size, margin or item field. Place its content with its own justify_content, align_items and padding.
Field names are CSS property names in snake_case, and keywords are CSS keywords in snake_case: "justify_content": "space_between". The value strings of grid track lists and lines keep CSS spelling: "repeat(auto-fill, minmax(160px, 1fr))". A field that doesn’t apply where it is written is refused with the reason, for example root.children[0] sets grid_column, a grid item field, in a flex container.
| Written | Means |
|---|---|
0.05 |
A fraction of the picture’s shorter side, so the same numbers keep their proportions on a 16:9 and a 9:16 canvas. |
"12px" |
Pixels of the project canvas. |
"50%" |
A percent of the parent’s content box, for sizes and insets only. |
padding, margin and inset take the CSS shorthand ("8px 16px") or a sides object. Colors may be CSS hex ("#1e2230") or the { red, green, blue, alpha } object.
"display": "grid" lays the children out in a CSS grid. This dashboard has two text cells and a bar that spans both columns:
{ "id": "dashboard", "kind": "generated", "generator": { "type": "layout", "root": { "display": "grid", "grid_template_columns": "1fr 1fr", "gap": { "mode": "constant", "value": "24px" }, "padding": { "mode": "constant", "value": "48px 64px" }, "background": { "mode": "constant", "value": "#101418" }, "font_family": "Hanken Grotesk", "fill": "#f5f5f0", "children": [ { "text": { "mode": "constant", "value": "Revenue" }, "font_size": { "mode": "constant", "value": "48px" } }, { "text": { "mode": "constant", "value": "Margin" }, "font_size": { "mode": "constant", "value": "48px" }, "fill": "#ffb020" }, { "grid_column": "1 / -1", "height": { "mode": "constant", "value": "50%" }, "background": { "mode": "constant", "value": "#1e2230" }, "border_radius": { "mode": "constant", "value": "12px" } } ] } }}Track lists support lengths, percents, fr, auto, min-content, max-content, minmax(), fit-content() and repeat() (with a count, auto-fill or auto-fit). Named lines and grid_template_areas are not supported.
A box’s text is one paragraph, wrapped at the box’s content width. The text style is inherited as in CSS: set font_family on the root and every text below uses it. Text is never shrunk to fit, and a text may change over time only by hold keyframes, because strings do not interpolate.
Animation
Section titled “Animation”Numeric values are keyed in the picture’s time, as shapes are. opacity, translate, scale and rotation_degrees are drawn after layout, about the box’s centre, and never move other boxes, so animate them for smooth motion: boxes are laid out in whole render pixels, and a slowly animated size moves in pixel steps. The lower third above fades and slides its card in with opacity and translate. A layout without keyframes is laid out once and every later frame reuses it.
Measure before you render
Section titled “Measure before you render”measureLayout in alpha2/layout lays a layout out with given fonts, at a given picture size and time, as a frame lays it out. It answers each node’s border and content boxes as fractions of the picture, its line count and baselines, and whether it overflows: its text is taller than its box or splits a word, or a child reaches past its padding box. Rendering draws overflowing boxes anyway, so measure and fix the document.
import init, { WasmTextMeasurer } from "alpha2/wasm";import { measureLayout } from "alpha2/layout";
await init();const measurer = new WasmTextMeasurer();measurer.addFont("Hanken Grotesk", fontBytes, 0);const picture = { width: 1280, height: 720 };const { nodes } = measureLayout(measurer, generator, picture, 0, picture);const overflowing = nodes.filter((node) => node.overflow).map((node) => node.at);Natively, alpha2_engine::measure_layout answers the same, to the bit; in Python, alpha2.TextMeasurer does. Measure at the size the picture is drawn at: at another size lines can break elsewhere.
alpha2/layout also writes layouts from CSS-like props with box, text and shape, and its output is a plain document that validateProject accepts. The Python package has the same builder in alpha2.layout.
