Pipeline

Docraft’s loom engine computes a document in four visitor passes over the node tree (see About Docraft): MeasureLayoutPaginateRender. 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
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

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

DocraftLoomMeasureProcessor(const std::shared_ptr<backend::IDocraftTextRenderingBackend> &text_backend)

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) &#8212; 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*)) &#8212; 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
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

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 &#8212; if it’s a DocraftLoomTable that alone doesn’t fit &#8212; 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” &#8212; 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()).

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”).

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

DocraftLoomPipelineExecutor(std::shared_ptr<backend::IDocraftTextRenderingBackend> text_backend, float page_width)

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 node is a nodes::DocraftLoomNode &#8212; 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_width across count items by weight &#8212; a missing (short weights) 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 count items.

  • 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:

count resolved widths.