Containers
Structural nodes that arrange children — stacks, tables, and lists. Note
there is no DocraftLoomForeach node: <Foreach> is expanded away
during tree building (see Craft Language Parser), not represented in the node
tree at all.
DocraftLoomLayoutContainer
Shared base for Rectangle/VStack/HStack — factors out spacing() and the
CSS-margin-collapsing gap resolution between adjacent children (see
DocraftLoomNode::margin() in Node Tree — Base & Text).
-
class DocraftLoomLayoutContainer : public docraft::loom::nodes::DocraftLoomShape
Abstract base for loom nodes that stack children with a gap between each pair of them and can optionally paint a background/border behind that stack.
Factors out what
DocraftLoomRectangle,DocraftLoomVStack, andDocraftLoomHStackall need identically —spacing()/set_spacing(), plusstyle()/edit_style()inherited fromDocraftLoomShape— so those three don’t each duplicate the same getter/setter pairs. Never instantiated directly; each subclass picks its own default spacing (Rectangle and VStack want visible breathing room by default, HStack’s shrink-to-fit columns don’t).Subclassed by docraft::loom::nodes::DocraftLoomHStack, docraft::loom::nodes::DocraftLoomRectangle, docraft::loom::nodes::DocraftLoomVStack
Public Functions
-
~DocraftLoomLayoutContainer() override = default
-
float spacing() const
-
void set_spacing(float spacing)
-
virtual float resolve_outer_margin(const DocraftLoomNode &node, bool leading)
The gap this container must reserve outside its first (leading=true) or last (leading=false) child — that child’s own edge margin, with no sibling on that side to combine it with via resolve_child_gap().
0 if node has no children. Default reads the vertical edges (top/bottom), shared as-is by Rectangle/VStack; DocraftLoomHStack overrides this with its own (left/right) edges.
-
float effective_padding() const
The padding actually used for measurement/layout.
Public Static Functions
-
static float resolve_child_gap(float container_spacing, float margin_a, float margin_b)
The gap actually placed between two adjacent children, given the two touching edges (e.g.
child A’s bottom margin and child B’s top margin for a vertical stack, or A’s right and B’s left for a horizontal one).
Public Static Attributes
-
static constexpr float kDefaultPadding = 10.0F
Default inset (points) applied to padding() by the constructor below, so a Rectangle/VStack/HStack that paints a border/background doesn’t have its children’s content sit flush against that border unless the craft document explicitly overrides it via a
paddingattribute (set_padding()).
-
~DocraftLoomLayoutContainer() override = default
DocraftLoomRectangle
<Rectangle> — a DocraftLoomLayoutContainer with explicit
width/height that stacks children top-to-bottom, plus
background/border/padding (via DocraftLoomShape, see Shapes).
-
class DocraftLoomRectangle : public docraft::loom::nodes::DocraftLoomLayoutContainer
Represents a rectangular node in the Docraft Loom structure.
DocraftLoomRectangleis a specific implementation ofDocraftLoomLayoutContainerthat encapsulates properties and behavior for a rectangular shape. It supports visitor-based operations, background/border styling and inter-child spacing (both inherited from DocraftLoomLayoutContainer), and can hold children (inherited from DocraftLoomNode) that are laid out inside it, padded by padding() (also inherited from DocraftLoomNode — every node type has it, not just Rectangle).Subclassed by docraft::loom::nodes::DocraftLoomCanvas
DocraftLoomVStack / DocraftLoomHStack
<Layout orientation="vertical"|"horizontal"> — stack children top-to-
bottom or left-to-right. Only HStack supports weights (proportional
column sizing); a vertical Layout’s weights attribute is a no-op.
-
class DocraftLoomVStack : public docraft::loom::nodes::DocraftLoomLayoutContainer
Loom node that stacks its children vertically (top-to-bottom).
During the measure pass, width = max child width and height = sum of child heights plus spacing between each pair of adjacent children (both
spacing()and the optional background/borderstyle()are inherited from DocraftLoomLayoutContainer — this lets<Header>/<Body>/<Footer>sections, built as a DocraftLoomVStack by DocraftLoomTreeBuilder::build_section, keep supporting background_color/border_color/border_width without needing DocraftLoomRectangle’s unrelated width_/height_/padding_ fields) — unless an explicit height() is set, in which case that height wins outright (mirrors DocraftLoomRectangle’s own explicit width() override). During the layout pass, each child is placed below the previous one, using its own natural height — unless weights() is non-empty AND height() is explicitly set, in which case that fixed height is instead divided among children by weight (missing/non-positive entries default to 1.0, i.e. equal/homogeneous division), mirroring DocraftLoomHStack’s weighted-width distribution. Unlike HStack, a plain VStack has no ambient “page height” budget to divide (pagination makes vertical space effectively unbounded) — so weights() alone has nothing well-defined to divide until the author also gives the VStack its own explicit height.Public Functions
-
DocraftLoomVStack()
-
~DocraftLoomVStack() override = default
-
virtual void accept(interfaces::DocraftLoomIVisitor &visitor) override
-
float height() const
-
void set_height(float height)
-
void set_weights(std::vector<float> weights)
Sets per-child weights used to divide an explicit height() among children.
Empty (the default) keeps today’s shrink-to-fit behavior, where each child simply gets its own natural height.
-
const std::vector<float> &weights() const
-
std::vector<float> resolve_vertical_child_gaps(const DocraftLoomNode &node, float container_spacing)
DocraftLoomLayoutContainer::resolve_child_gap() applied to every adjacent pair of node’s children at once, using each child’s bottom/top margin (the vertical-stacking counterpart of DocraftLoomHStack::resolve_horizontal_child_gaps()).
-
DocraftLoomVStack()
-
class DocraftLoomHStack : public docraft::loom::nodes::DocraftLoomLayoutContainer
Loom node that places its children horizontally (left-to-right).
During the measure pass, height = max child height and width = sum of child widths plus spacing between each pair of adjacent children (both
spacing()and the optional background/borderstyle()are inherited from DocraftLoomLayoutContainer). During the layout pass, each child is placed to the right of the previous one, using its own natural width — unless weights() is non-empty, in which case the available content width is instead divided among children by weight (missing/non-positive entries default to 1.0, i.e. equal/homogeneous division), mirroring DocraftLoomTable’s column_weights.Public Functions
-
DocraftLoomHStack()
-
~DocraftLoomHStack() override = default
-
virtual void accept(interfaces::DocraftLoomIVisitor &visitor) override
-
void set_weights(std::vector<float> weights)
Sets per-child weights used to divide the available content width.
Empty (the default) keeps today’s shrink-to-fit behavior, where each child simply gets its own natural width.
-
const std::vector<float> &weights() const
-
std::vector<float> resolve_horizontal_child_gaps(const DocraftLoomNode &node, float container_spacing)
DocraftLoomLayoutContainer::resolve_child_gap() applied to every adjacent pair of node’s children at once, using each child’s right/left margin (this is the horizontal-stacking counterpart of the vertical, per-pair calls Rectangle/VStack make directly)
-
virtual float resolve_outer_margin(const DocraftLoomNode &node, bool leading) override
The gap this container must reserve outside its first (leading=true) or last (leading=false) child — that child’s own edge margin, with no sibling on that side to combine it with via resolve_child_gap().
0 if node has no children. Default reads the vertical edges (top/bottom), shared as-is by Rectangle/VStack; DocraftLoomHStack overrides this with its own (left/right) edges.
-
DocraftLoomHStack()
DocraftLoomTable / DocraftLoomTableCell
<Table> — a grid of cells with column weights, optional per-cell
background, title cells (painted last, over dividers), and
split_after_row() support for pagination across pages. Cell content must
be DocraftLoomText or DocraftLoomImage; absolute positioning inside a
cell throws InvalidInputException at layout time.
-
class DocraftLoomTable : public docraft::loom::nodes::DocraftLoomNode
Table node: a grid of DocraftLoomTableCell nodes.
There is no separate “header row/column” concept — a cell may be marked is_title() (a property on the cell itself), which only affects draw order in Rendering (title cells paint last, over any dividers). Measure/Layout treat every cell uniformly regardless of is_title — this is what lets the same table represent both a “column headers” (title row) and a “row labels” (title column) shape with one code path.
Column-width resolution is computed by the layout engine (DocraftLoomLayoutProcessor) and written into each cell’s own frame.size — the table itself holds no separate width/height cache, mirroring how every other container (VStack/HStack/Rectangle) derives its rendering geometry from its children’s own LayoutBox rather than a parallel cache.
Public Functions
-
DocraftLoomTable() = default
-
~DocraftLoomTable() override = default
-
virtual void accept(loom::interfaces::DocraftLoomIVisitor &visitor) override
- Throws:
exception::InvalidInputException – if any cell is null, or if the row’s column count doesn’t match previously added rows.
-
int row_count() const
-
int column_count() const
-
std::shared_ptr<DocraftLoomTableCell> cell(int row, int column) const
-
float row_height(int row) const
Row row’s height, once Layout has resolved it: every cell in a row shares the same frame.size.height (place_table_cells stretches each row to its tallest cell), so cell(row, 0) alone is always representative — this is the single named accessor for that convention, instead of every call site re-deriving it from cell(row, 0)->layout_box().frame.size.height by hand.
- Returns:
0 if row is out of range or the table has no columns.
-
template<typename Fn>
inline void for_each_cell(Fn &&fn) const Invokes fn(DocraftLoomTableCell&) for every cell in the grid, in row-major order.
Cells live in the table’s own grid_, not the inherited children_ vector — generic tree walks over a node’s children (page-index assignment, position shifts, …) must use this instead of children_count()/ edit_child() to reach a table’s cells.
-
void set_column_weights(std::vector<float> weights)
-
const std::vector<float> &column_weights() const
-
void set_default_cell_background(const DocraftColor &color)
-
const std::optional<DocraftColor> &default_cell_background() const
-
float baseline_offset() const
-
void set_baseline_offset(float baseline_offset)
-
float padding() const
Outer spacing around the whole table (all four sides), mirroring how DocraftLoomRectangle’s own padding() insets its children from its border — here there’s no border to speak of, so it simply keeps the grid from sitting flush against whatever precedes/follows the table (or the page edge).
Wired to the common
paddingcraft-language attribute like any other node that exposes a setter (see DocraftLoomTreeBuilder::apply_common_attributes).
-
void set_padding(float padding)
-
std::shared_ptr<DocraftLoomTable> split_after_row(int row_index, bool repeat_header_rows = true)
Splits off rows [row_index, row_count()) into a new table (mirrors legacy’s DocraftTable::split_after_row), for use when a table doesn’t fit on one page.
Column weights/default background/baseline offset are copied onto the remainder. If repeat_header_rows is true, a leading contiguous run of all-title rows in this table (before row_index) is cloned onto the front of the remainder too, so a header row keeps repeating on the continuation.
- Returns:
The remainder table, or nullptr if row_index is 0 or >= row_count() (nothing to split).
-
int leading_title_row_count() const
Length of the leading contiguous run of all-title rows (0 if row 0 isn’t a title row, or the table has no rows).
This is exactly what split_after_row(…, repeat_header_rows=true) clones onto a remainder’s front as its repeating header, so callers can tell whether a given number of “fitting” rows covers only repeated header rows or actual content.
-
std::shared_ptr<DocraftLoomTable> split_row_content(int row_index, float available_height, bool repeat_header_rows = true)
Splits row row_index’s own cell content by wrapped-line count, for the case where the row alone is taller than available_height (typically a whole page) and can’t be moved elsewhere as a unit.
For each cell whose content is a DocraftLoomText with 2+ wrapped_lines(), as many leading lines as fit within available_height stay in *this (row_index shrinks to just that fragment); the rest become that column’s cell in a new one-row remainder table (repeated header rows cloned onto its front first, mirroring split_after_row), the same way this table’s own leftover rows (if any, after row_index) do. A cell whose content isn’t a splittable Text — or whose sole line still exceeds available_height — must already fit within available_height on its own, or nothing is split at all.
- Returns:
The remainder table, or nullptr if row_index is out of range, the table has no columns, no cell’s content could be split, or every cell already fit whole (nothing to split).
-
DocraftLoomTable() = default
-
class DocraftLoomTableCell : public docraft::loom::nodes::DocraftLoomNode
A single table cell — a node like any other.
Its content is just its child (set via set_content(), which enforces the same Text/Image-only restriction as legacy tables). background/explicit_width/is_title are properties of the cell itself, not of the table.
Public Functions
-
DocraftLoomTableCell() = default
-
~DocraftLoomTableCell() override = default
-
virtual void accept(loom::interfaces::DocraftLoomIVisitor &visitor) override
Sets the cell’s content, replacing any existing child.
- Throws:
exception::InvalidInputException – if content is neither a DocraftLoomText nor a DocraftLoomImage.
-
std::shared_ptr<DocraftLoomNode> content()
-
const std::optional<DocraftColor> &background() const
-
void set_background(const DocraftColor &color)
-
std::optional<float> explicit_width() const
-
void set_explicit_width(float width)
-
bool is_title() const
-
void set_is_title(bool is_title)
-
DocraftLoomTableCell() = default
DocraftLoomList
<List>/<UList> — ordered (number/roman) or unordered (dash/star/
circle/box) markers; items are plain DocraftLoomText children.
-
class DocraftLoomList : public docraft::loom::nodes::DocraftLoomNode
Loom node representing a list.
Items are DocraftLoomText children (added via the inherited add_child()); each item gets a Marker computed during Measure (text/kind/width) and positioned during Layout.
Public Functions
-
DocraftLoomList() = default
-
~DocraftLoomList() override = default
-
virtual void accept(loom::interfaces::DocraftLoomIVisitor &visitor) override
-
OrderedListStyle ordered_style() const
-
void set_ordered_style(OrderedListStyle style)
-
UnorderedListDot unordered_dot() const
-
void set_unordered_dot(UnorderedListDot dot)
-
bool marker_is_box() const
-
std::string marker_text_for_index(int index) const
Returns the marker text for a 0-based item index (e.g.
“1.”, “IV.”, “*”), mirroring legacy’s DocraftList::prefix_for_index/roman_for. Box markers have no text (an empty string), the box itself is drawn instead.
-
float marker_gap() const
-
struct Marker
-
DocraftLoomList() = default