Atelier

AtelierLayout

Compose precise SVG layouts from geometry.

The shared geometry layer before rendering. Solve grids, labels, tracks, and connections once, then draw the resulting rectangles and points in SVG, Canvas, or any other renderer.

headersidebarcontentfooter2 columns128 fixed468 flexible
A 128-unit sidebar and a flexible column share 608 units, with a 12-unit gap. The header spans both columns; the sidebar spans two rows. SVG draws the rectangles returned by the solver.

Features

  • Geometry, never markup
    Rect, Point, Size, Insets, Bounds and Circle are immutable values with no behaviour beyond their own maths. Nothing here emits SVG, HTML or a DOM.
  • Composition primitives
    Stacks along one axis, grids on two, groups that carry a bounding box, overlays anchored to a corner, spacers, alignment and distribution.
  • Solved in one pass
    Constraints travel down the node tree, sizes travel back up, and the solved tree answers by name: frameOf('title').
  • Text is geometry
    Wrapping, per-line frames, baselines, and overflow reported rather than hidden. The default measurer is deterministic on purpose, so tests and server output do not vary with the fonts installed.
  • Links between boxes
    Where a link leaves one rectangle, where it enters another, and the right-angle path between: points, a label position, and the final tangent for an arrowhead.
  • Lanes, strips and keys
    Parallel lanes with a reserved header, an edge strip for an axis, and a key made of swatches and labels. Each takes a rectangle and returns placed frames.

Solve a real grid

use Atelier\Layout\Element\Frame;
use Atelier\Layout\Element\Grid;
use Atelier\Layout\Geometry\Rect;
use Atelier\Layout\Grid\TrackSize;
use Atelier\Layout\LayoutContext;

$grid = Grid::tracks('workspace', [TrackSize::fixed(128), TrackSize::fr()])
    ->rows([TrackSize::fixed(52), TrackSize::fr(), TrackSize::fixed(40)])
    ->gap(12)
    ->add(Frame::stretch('header'), columnSpan: 2)
    ->add(Frame::stretch('sidebar'), rowSpan: 2)
    ->add(Frame::stretch('content'))
    ->add(Frame::stretch('footer'));

$result = $grid->solve(new LayoutContext(), new Rect(16, 16, 608, 260));
$content = $result->frameOf('content'); // Rect(156, 80, 468, 144)

Wrap a label before drawing it

use Atelier\Layout\Element\TextBlock;

$label = TextBlock::of('summary', 'A label that wraps predictably', 12)
    ->breakWords()
    ->layout(new LayoutContext(), new Rect(0, 0, 120, 48));

$label->lines;
$label->hasOverflow();

Connect two boxes

use Atelier\Layout\Connection\OrthogonalConnector;
use Atelier\Layout\Geometry\Rect;

$from = new Rect(20, 20, 80, 40);
$to = new Rect(180, 60, 90, 40);

$connection = (new OrthogonalConnector())->connect($from, $to);

Configuration

LayoutContext accepts a text measurer. Its default is deliberately deterministic, so server-side output and snapshot tests do not vary by installed fonts. Swap in a precise measurer when your renderer needs it.

Documentation

Start

Composition

  • Composition: combine one-dimensional and two-dimensional layout primitives.
  • Stacks and Grid: arrange children along one axis or across tracks.
  • Alignment and Distribution: position children and share remaining space.
  • Groups and Overlays: compose solved bounds and anchor one element to another.

Geometry and connections