Pipeline
Docraft’s loom engine computes a document in four visitor passes over the
node tree (see About Docraft): Measure → Layout → Paginate →
Render. Each stage implements DocraftLoomIVisitor — one
visit(NodeType*) overload per node type, dispatched via double-dispatch
(node->accept(visitor)) — and writes exactly one field of each node’s
LayoutBox (see Node Tree — Base & Text).
Visitor Interfaces
Adding a new node type means adding a visit() overload here and
implementing it in all four processors below — a compile error catches any
processor left behind.
-
class DocraftLoomIVisitor
Visitor interface for DocraftLoom nodes.
Subclassed by docraft::loom::pipeline::DocraftLoomLayoutProcessor, docraft::loom::pipeline::DocraftLoomMeasureProcessor, docraft::loom::pipeline::DocraftLoomPaginationProcessor, docraft::loom::pipeline::DocraftLoomRenderingProcessor
Public Functions
-
virtual ~DocraftLoomIVisitor() = default
-
virtual void visit(docraft::loom::nodes::DocraftLoomText*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomTitle*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomSubtitle*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomRectangle*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomCanvas*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomParagraph*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomVStack*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomHStack*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomBlankLine*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomImage*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomLine*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomCircle*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomTriangle*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomPolygon*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomList*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomTableCell*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomTable*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomPageNumber*) = 0
-
virtual void visit(docraft::loom::nodes::DocraftLoomNewPage*) = 0
-
virtual ~DocraftLoomIVisitor() = default
-
class DocraftLoomIVisitorNode
Accepter interface for DocraftLoom nodes that can accept a DocraftLoomIVisitor.
Note
This class will be implemented by all DocraftLoom nodes that can be visited by a DocraftLoomIVisitor.
Subclassed by docraft::loom::nodes::DocraftLoomNode
Public Functions
-
virtual ~DocraftLoomIVisitorNode() = default
-
virtual void accept(DocraftLoomIVisitor&) = 0
-
virtual ~DocraftLoomIVisitorNode() = default
DocraftLoomMeasureProcessor
Computes layout_box().measured_size for every node, bottom-up (children
before parent). Needs a text-rendering backend for font-metric-based
measurement and word-wrapping.
-
class DocraftLoomMeasureProcessor : public docraft::loom::interfaces::DocraftLoomIVisitor
Implementation of the DocraftLoomIVisitor interface for measuring Loom nodes.
Public Functions
Constructor for the DocraftLoomMeasureProcessor class.
-
void set_content_width(float width)
Sets the width available to block-flow content for the region about to be measured (mirrors DocraftLoomLayoutProcessor::set_content_width) — needed so a weighted DocraftLoomHStack can resolve each column’s width and push it down to Text descendants as their wrap width, all before Layout ever runs.
Also clears any wrap-width constraint left over from a previous region’s traversal (see DocraftLoomPdfCreator::create(), which reuses one processor instance across header/footer/body): each region must start fresh, with no ancestor- pushed constraint carried over from an unrelated region.
-
~DocraftLoomMeasureProcessor() override = default
Destructor for the DocraftLoomMeasureProcessor class.
This destructor is defaulted and does not need to be overridden.
-
virtual void visit(docraft::loom::nodes::DocraftLoomText*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTitle*) override
Measured exactly like ordinary text (see visit(DocraftLoomText*)) — DocraftLoomTitle/DocraftLoomSubtitle only differ in their constructor’s default font/style/margin.
-
virtual void visit(docraft::loom::nodes::DocraftLoomSubtitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomRectangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCanvas*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomParagraph*) override
Measures the paragraph as the sum of its children’s heights plus inter-line spacing, plus space_before and space_after.
-
virtual void visit(docraft::loom::nodes::DocraftLoomVStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomHStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomBlankLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomImage*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCircle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTriangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPolygon*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomList*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTableCell*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTable*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPageNumber*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomNewPage*) override
DocraftLoomLayoutProcessor & DocraftLoomCursor
Walks the tree with a DocraftLoomCursor (origin top-left, y grows
downward), filling layout_box().frame. Layout happens on one continuous,
unbounded-height canvas — it never thinks about page boundaries.
-
class DocraftLoomLayoutProcessor : public docraft::loom::interfaces::DocraftLoomIVisitor
Implementation of the DocraftLoomIVisitor interface for processing Loom nodes during the layout phase.
Public Functions
-
explicit DocraftLoomLayoutProcessor(float page_width = 0.0F)
Constructs the layout processor.
- Parameters:
page_width – Width of the page in points, used to resolve block-flow width (e.g. blank lines, tables) and as the container width for absolute positioning fallbacks. Defaults to 0.0F (unconstrained) to preserve existing no-arg call sites.
-
void reset_cursor(float x, float y)
Resets the shared cursor to an explicit position, so the same processor instance can be reused across separate layout regions (header/body/footer) that each need their own starting point instead of the cursor’s default top margin.
-
void set_content_width(float width)
Sets the width available to block-flow content in the region about to be laid out (blank lines stretching to fill it, table column resolution).
Callers reusing the same processor across header/body/footer regions with different margins call this alongside reset_cursor() before each region’s layout pass.
-
virtual void visit(docraft::loom::nodes::DocraftLoomText*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomSubtitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomRectangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCanvas*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomParagraph*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomVStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomHStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomBlankLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomImage*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCircle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTriangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPolygon*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomList*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTableCell*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTable*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPageNumber*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomNewPage*) override
-
explicit DocraftLoomLayoutProcessor(float page_width = 0.0F)
-
class DocraftLoomCursor
Cursor class for tracking the current position in the layout process.
Zero is the top-left corner of the page, and coordinates increase to the right and down.
Public Functions
-
DocraftLoomCursor() = default
-
~DocraftLoomCursor() = default
-
float x() const
-
float y() const
-
void move(float dx, float dy)
Moves the cursor by the specified delta values in the x and y directions.
- Parameters:
dx –
dy –
-
void set_position(float x, float y)
Sets the cursor’s position to the specified coordinates.
- Parameters:
x –
y –
-
DocraftLoomCursor() = default
DocraftLoomPaginationProcessor
Fills layout_box().page_index by walking the body’s top-level children,
splitting an overflowing DocraftLoomTable row-by-row (with repeating
title rows) and moving any other overflowing child whole to a new page.
Header/footer are stamped page_index = -1 (render on every page) rather
than walked by this pass.
-
class DocraftLoomPaginationProcessor : public docraft::loom::interfaces::DocraftLoomIVisitor
Public Functions
-
virtual void visit(docraft::loom::nodes::DocraftLoomText*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomSubtitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomRectangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCanvas*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomParagraph*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomVStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomHStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomBlankLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomImage*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCircle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTriangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPolygon*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomList*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTableCell*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTable*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPageNumber*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomNewPage*) override
-
int paginate_body(nodes::DocraftLoomNode &body_root, float body_top_y, float body_height, backend::IDocraftPageRenderingBackend *page_backend)
Paginates the body’s top-level children (body_root’s own children_, in order): each child either fits on the current page as-is, moves whole to a new page, or — if it’s a DocraftLoomTable that alone doesn’t fit — gets split into a fragment that stays plus a remainder table inserted as the next sibling and placed on a new page (mirrors legacy’s DocraftTable::split_after_row).
Every node touched gets layout_box().page_index stamped recursively (0-based), so DocraftLoomRenderingProcessor can later draw only the content assigned to whichever page is currently being rendered. New physical pages are created via page_backend eagerly, as soon as an overflow is detected (mirroring legacy).
Scope note: only body_root’s direct children are pagination-aware; content nested deeper (e.g. a table inside a Rectangle) is not split and simply moves or stays as a whole unit. An oversized child that doesn’t fit even on a fresh page is left overflowing rather than looping forever creating empty pages.
- Parameters:
body_root – Root container of the body (typically a VStack).
body_top_y – Y coordinate (continuous layout space) of the body’s top on every page.
body_height – Height of the body region available on every page.
page_backend – Used to create additional physical pages as needed.
- Returns:
Total number of pages the body now spans (>= 1).
Public Static Functions
-
static void assign_page_index_recursive(nodes::DocraftLoomNode &node, int page_index)
Stamps page_index on a node and every descendant, recursively (including table cells, which live outside the inherited children_ vector).
Pass -1 to mark a subtree as “render on every page” — used for header/footer content and for the body’s own root container (whose direct children carry the real per-page indices assigned by paginate_body()).
-
virtual void visit(docraft::loom::nodes::DocraftLoomText*) override
DocraftLoomRenderingProcessor
Run once per physical page; paints only nodes whose page_index matches
the current page (or is -1). Pulls narrow capability interfaces
(text/shape/line/image rendering, see Rendering Backend) off an
IDocraftRenderingCapabilityProvider rather than depending on the concrete
Haru backend directly.
-
class DocraftLoomRenderingProcessor : public docraft::loom::interfaces::DocraftLoomIVisitor
Public Functions
-
explicit DocraftLoomRenderingProcessor(backend::IDocraftRenderingCapabilityProvider *backend = nullptr)
-
virtual void visit(docraft::loom::nodes::DocraftLoomText*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomSubtitle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomRectangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCanvas*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomParagraph*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomVStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomHStack*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomBlankLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomImage*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomLine*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomCircle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTriangle*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPolygon*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomList*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTableCell*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomTable*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomPageNumber*) override
-
virtual void visit(docraft::loom::nodes::DocraftLoomNewPage*) override
-
void set_current_page(int page_index, int total_page_count)
Sets which physical page is currently being rendered, so that should_render() gates paginated content correctly and DocraftLoomPageNumber can display the right value.
Call once per page, before re-running accept() on the document tree for that page.
- Parameters:
page_index – 0-based index of the page currently being drawn.
total_page_count – Total number of pages in the document (for “n of N”).
-
explicit DocraftLoomRenderingProcessor(backend::IDocraftRenderingCapabilityProvider *backend = nullptr)
DocraftLoomPipelineExecutor
Runs Measure→Layout for one region (header, body, or footer) with a fresh processor pair per call, so per-traversal state (inherited wrap width, inherited content width) never leaks between regions.
-
class DocraftLoomPipelineExecutor
Runs the Measure -> Layout half of the loom pipeline for one region (header/body/footer) of a page, given that region’s own content width and cursor origin.
Constructed once per DocraftLoomPdfCreator::create() call with the page-wide context (text backend, page width) shared by every region, then invoked once per region with that region’s own inputs.
Each run() call builds a fresh DocraftLoomMeasureProcessor/DocraftLoomLayoutProcessor pair rather than reusing one pair across regions: a region is a wholly separate traversal, and per-traversal state internal to those processors (e.g. DocraftLoomMeasureProcessor’s inherited_wrap_width_, DocraftLoomLayoutProcessor’s inherited_width_) must not leak from one region into the next. A fresh instance guarantees that structurally, rather than relying on set_content_width() to know about and reset every such field.
Public Functions
Constructs the executor with the context shared by every region.
- Parameters:
text_backend – Backend used for text measurement.
page_width – Full page width, used to construct each region’s DocraftLoomLayoutProcessor (constrains its incoming_width() at the root).
-
void run(interfaces::DocraftLoomIVisitorNode &node, float content_width, float cursor_x, float cursor_y, bool assign_fixed_page_index = false) const
Measures then lays out
node’s subtree, mutating each node’s layout_box.- Parameters:
node – Region root (header/footer/body) to measure and lay out.
content_width – Width available to this region’s content (page_width minus that region’s own margins).
cursor_x – Region content origin’s x (after that region’s own left margin).
cursor_y – Region content origin’s y (after that region’s own top margin).
assign_fixed_page_index – Optional. When true, once layout completes, stamps every node in the subtree with page_index -1 (via DocraftLoomPaginationProcessor::assign_page_index_recursive()) if
nodeis a nodes::DocraftLoomNode — for header/footer, which are laid out once and re-rendered on every physical page rather than participating in the body’s per-page pagination (DocraftLoomPaginationProcessor::paginate_body()), so the body must leave this false (the default).
Weighted Column Distribution
Shared helper used by Measure and Layout to resolve weights on a
horizontal <Layout> or <Table> column set — divides available width
by weight (missing/non-positive entries default to an even 1.0 share), with
optional per-item floors so no result goes below a natural minimum.
-
std::vector<float> docraft::loom::pipeline::distribute_weighted_widths(float available_width, const std::vector<float> &weights, int count, const std::vector<float> &floors = {})
Divides
available_widthacrosscountitems by weight — a missing (shortweights) or non-positive entry defaults to 1.0, i.e.an even share. Shared by DocraftLoomMeasureProcessor and DocraftLoomLayoutProcessor’s HStack and Table weighted-column code, so both stages resolve the same weights the same way.
- Parameters:
available_width – Total width to divide among the
countitems.weights – Per-item weight; a missing or non-positive entry defaults to 1.0.
count – Number of items to resolve a share for.
floors – Optional per-item floor (e.g. each item’s own natural/measured width); if non-empty (same size as
count), no result is smaller than its floor.
- Returns:
countresolved widths.