NAME
Layout::Flex - CSS flexbox layout engine with wrap, gap, margin, and content driven sizing
VERSION
Version 0.02
SYNOPSIS
use Layout::Flex;
# Explicit sizing
my @rects = Layout::Flex->compute(
main_size => 400,
cross_size => 300,
direction => 'row',
justify => 'space-between',
align => 'stretch',
wrap => 'wrap',
align_content => 'space-around',
gap => 10,
items => [
{ basis => 150, grow => 1, cross => 80, margin => 5 },
{ basis => 150, grow => 1, cross => 60, margin => 5 },
],
);
# Content driven sizing with a measure callback
my @rects = Layout::Flex->compute(
main_size => 400,
cross_size => 200,
measure => sub {
my ($item, $avail_w) = @_;
# $item hashref has text, font_size, and any custom keys you set
# $avail_w is defined only during the wrap second pass
my ($w, $h) = your_font_metrics($item->{text}, $item->{font_size});
return ($w, $h);
},
items => [
{ text => 'Hello', font_size => 14, grow => 1 },
{ text => 'Paragraph', font_size => 12, grow => 2, wrap_text => 1 },
],
);
for my $r (@rects) {
my ($x, $y, $w, $h) = @$r;
}
DESCRIPTION
Layout::Flex implements the CSS Flexible Box layout algorithm in C/XS. Given a container size and a list of flex items it computes the exact position and size of each item.
Both single line (nowrap, default) and multi line (wrap, wrap-reverse) modes are supported. In multi line mode, items that overflow the main axis are collected onto new lines; each line runs an independent grow/shrink pass and lines are then distributed across the cross axis according to align_content.
gap (or main_gap/cross_gap) inserts fixed space between items and between lines before justify/align_content distributes any remaining free space. Per-item margins (margin, margin_top, margin_right, margin_bottom, margin_left) offset items within their slot; the output rect is always the content box, excluding margins.
Content-driven sizing is supported via a measure callback. Items may carry a text key (and font_size, plus any custom keys your callback needs); the engine calls the callback to derive basis and cross before layout runs. Items with wrap_text => 1 trigger a second measurement pass once their resolved width is known, allowing line-breaking callbacks to return the correct wrapped height.
METHODS
compute
my @rects = Layout::Flex->compute(%args);
Compute layout. Returns a list of [$x, $y, $w, $h] array references, one per item, in the same order as items.
Container options
main_size-
Size of the container along the main axis: width for
row, height forcolumn. cross_size-
Size of the container along the cross axis: height for
row, width forcolumn. direction-
row(default) — items flow left to right.column— items flow top to bottom. justify-
Distributes free space along the main axis. One of:
align-
Sizes and positions items along the cross axis within their line. One of:
wrap-
Controls whether items may wrap onto multiple lines. One of:
align_content-
Distributes lines across the cross axis when
wrapproduces more than one line. Has no effect in single-line layouts. One of:stretch(default) — lines stretch equally to fill the container cross sizestart— lines packed at the start of the cross axisend— lines packed at the endcenter— lines centredspace-between— equal gaps between lines, none at edgesspace-around— equal gaps around lines (half-gap at edges)space-evenly— equal gaps between lines and edges
gap-
Shorthand that sets both
main_gapandcross_gapto the same value. main_gap-
Fixed space inserted between items within each line on the main axis. Reduces free space before
justifydistributes any remainder. Default0. cross_gap-
Fixed space inserted between lines on the cross axis. Reduces free space before
align_contentdistributes any remainder. Has no effect in single-line layouts. Default0.
Item options
Each entry in items is a hashref with:
basis-
Initial main-axis size before grow/shrink is applied. Default
0. grow-
flex-growfactor. The item receives this fraction of positive free space relative to the sum of allgrowvalues. Default0. shrink-
flex-shrinkfactor. When items overflow the container this controls how much each item gives back. Default1. min_main,max_main-
Clamp the resolved main-axis size.
max_mainof0means unconstrained. Default0for both (unconstrained minimum). cross-
Natural cross-axis size. Used when
alignoralign_selfisstart,end, orcenter. Ignored understretch. Default0. min_cross,max_cross-
Clamp the resolved cross-axis size.
max_crossof0means unconstrained. align_self-
Per-item override for
align. Accepts the same values (stretch,start,end,center). Omit or leave undefined to inherit the container'salign. margin-
Shorthand that sets all four margins to the same value.
margin_top,margin_right,margin_bottom,margin_left-
Per-side margins. These are direction-aware: in
rowlayout,margin_left/margin_rightaffect the main axis andmargin_top/margin_bottomaffect the cross axis; incolumnlayout the axes are swapped.Margins reduce the free space available for grow/shrink and count toward line-breaking decisions. The output rect is always the content box — margins are factored into spacing but are not included in
x/y/w/h.
Content sizing
When a measure callback is supplied on the container, items that carry a text key are automatically sized before layout runs. The following item options control this behaviour.
text-
The text content of the item. When
measureis set and this key is present, the engine calls the measurer to determinebasis(main-axis natural size) andcross(cross-axis natural size). Any explicitbasisorcrosson the item takes precedence and suppresses the corresponding measurement. font_size-
Font size in points, passed to the measurer. Default
12. The built-in'simple'measurer usesfont_size * 0.6as character width andfont_size * 1.4as line height. wrap_text-
Boolean. When true, the item participates in a second measurement pass after
lf_computehas resolved final widths. The measurer is called again with the resolved width as a second argument$avail_w, allowing it to return a line-wrapped height. A secondlf_computethen runs to apply the new cross sizes.For row layouts the resolved width is
$rect->{w}; for column layouts it is$rect->{h}. The built-in'simple'measurer calculatesceil(len / floor(avail_w / char_w))lines and multiplies by line height.A code-ref measurer receives
($item, $avail_w)on the second pass and should return($w, $h); the engine uses the returned$h(and$wif provided) to update the item's cross size.Items without
wrap_textare measured only once; they keep their naturally resolved dimensions.
Container measure option
measure-
One of:
'simple'-
Built-in proportional approximation. Width =
length($text) * $font_size * 0.6. Height =$font_size * 1.4. Useful for fast layout estimates without a real font engine. - A code reference
-
measure => sub { my ($item, $avail_w) = @_; # $avail_w is undef on the first pass; defined on the wrap_text second-pass ... return ($w, $h); }Called once per item that has a
textkey. On the first pass$avail_wisundef; on thewrap_textsecond pass it is the resolved main-axis size. The item hashref containstext,font_size, and any other keys you set on the item.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report bugs at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Layout-Flex.
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)