Skip to content
getnextpdf.com

Pro edition

Flow Layout — Deep Reference

This page is the deep reference for the Pro Flow Layout module. It covers the placement engine, the element model, the page-break strategies, their behavior contracts, and their failure modes. StreamingLayoutEngine walks a list of FlowElement values in order. It assigns each one a zero-based page index and a position inside a LayoutRegion. The outcome is a LayoutResult of immutable PlacedElement records. The module computes placement only; it renders nothing and performs no I/O.

This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.

No per-feature license flag exists. This is a Pro-edition capability.

All symbols live in the NextPDF\Pro\FlowLayout namespace. All value objects are final and immutable.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
StreamingLayoutEngine::__constructLayoutRegion $region, PageBreakStrategy $strategy = PageBreakStrategy::GreedyBinds a per-page content area to a break strategyStreamingLayoutEngineStrategy defaults to Greedy.
StreamingLayoutEngine::layoutlist<FlowElement> $elementsSingle forward pass; sequential placement with strategy-driven page breaksLayoutResultNever throwsAn empty list yields one empty page.
StreamingLayoutEngine::withStrategyPageBreakStrategy $strategyDerives a new engine with the same regionselfThe receiver is unchanged.
StreamingLayoutEngine::withRegionLayoutRegion $regionDerives a new engine with the same strategyselfThe receiver is unchanged.
FlowElement::__constructFlowElementType $type, string $content, float $widthPt = 0, float $heightPt = 0, float $marginTopPt = 0, float $marginBottomPt = 0, bool $keepWithNext = falseImmutable element value objectFlowElementOnly construction path for Table elements.
FlowElement::textstring $content, float $heightText element with a caller-measured heightself (static)Width 0 resolves to the region width at placement.
FlowElement::imagestring $path, float $width, float $heightImage element; content carries the pathself (static)The engine never opens the file.
FlowElement::spacerfloat $heightVertical whitespace with empty contentself (static)
FlowElement::pageBreakExplicit break markerself (static)Emits no PlacedElement.
FlowElement::totalHeightHeight plus top and bottom marginsfloatAll fit checks use this value.
FlowElementTypeenum cases Text, Image, Table, Spacer, PageBreakString-backed: text, image, table, spacer, page_break
FlowElementType::isBreakableText and Table return true; others return falseboolClassification only; see the atomic-placement contract below.
LayoutRegion::__constructfloat $x, float $y, float $width, float $heightTop-left-origin content box, measured in pointsLayoutRegionNo validation; values are taken as given.
LayoutRegion::containsfloat $px, float $pyBoundary-inclusive point-in-region testbool
LayoutRegion::remainingHeightfloat $currentYRegion height minus the consumed vertical offsetfloatZero or negative once the cursor has overflowed.
LayoutResult::__constructlist<PlacedElement> $placements, int $pageCount, float $totalHeightPtImmutable layout outcomeLayoutResult
LayoutResult::placementsOnPageint $pageIndexFilters placements by zero-based page indexlist<PlacedElement>The returned list is re-indexed.
LayoutResult::isEmptyTrue when no elements were placedboolTrue for empty and break-only input.
PageBreakStrategyenum cases Greedy, AvoidOrphans, KeepTogetherString-backed: greedy, avoid_orphans, keep_together
PageBreakStrategy::labelHuman-readable strategy labelstring
PlacedElement::__constructFlowElement $element, int $pageIndex, float $x, float $y, float $width, float $heightImmutable placement recordPlacedElementCoordinates are in points, top-left origin.
public function layout(array $elements): LayoutResult
public function withStrategy(PageBreakStrategy $strategy): self
public function withRegion(LayoutRegion $region): self
public static function text(string $content, float $height): self
public static function image(string $path, float $width, float $height): self
public static function spacer(float $height): self
public static function pageBreak(): self

StreamingLayoutEngine::layout() performs one forward pass over the input list. For each element it checks the fit, breaks the page when required, then records a PlacedElement. An empty input list returns a LayoutResult with no placements, a page count of 1, and a total height of 0.

Placement geometry is deterministic:

  • x is the region’s left edge.
  • y is the current cursor position plus the element’s top margin.
  • width is the element’s widthPt when positive, otherwise the region width.
  • height is the element’s heightPt, exactly as supplied.

After each placement the cursor advances by totalHeight(), margins included. The same amount accumulates into LayoutResult::totalHeightPt.

Page-break rules, in evaluation order:

  • An explicit PageBreak element increments the page index and resets the cursor to the region top. It emits no placement and adds nothing to the total height.
  • When an element’s totalHeight() exceeds the remaining height, the engine breaks — unless the cursor is already at the top of the page.
  • Greedy adds no further condition: fitting elements are always placed.
  • AvoidOrphans breaks before a fitting element when the space left after placement would be positive yet below half the element’s own required height. The element’s own height is the reference unit, with a fixed divisor of two; no font metric is involved. It never breaks at the top of a page.
  • KeepTogether breaks before a fitting element when its keepWithNext flag is set, a next element exists, the cursor is not at the page top, and the combined totalHeight() of both elements exceeds the remaining space. The flag on the final element has no effect.

Atomic placement: the engine places every element as a unit. It never splits element content across pages. FlowElementType::isBreakable() classifies which types a caller may pre-split into smaller elements; the engine itself does not consult it.

Statelessness and determinism: the engine holds only its region and strategy. layout() shares no state between calls, and identical inputs produce identical results. withStrategy() and withRegion() return new engines and never mutate the receiver.

  • No method in this module throws. There is no exception hierarchy to catch.
  • Constructors validate nothing. Negative or zero region dimensions, negative element heights, and negative margins are accepted and flow through the arithmetic unchanged.
  • An element taller than the region is still placed. At the top of a page it is placed there and overflows; elsewhere the engine breaks first and it overflows a fresh page. The next element then always triggers a break, so overflow is confined to one page.
  • A leading PageBreak places the first content element on page index 1, giving a page count of at least 2.
  • Consecutive PageBreak elements each advance the page counter, producing blank pages. A trailing one leaves a final empty page in pageCount.
  • Keep-together holds only when both paired elements fit on one page together. A pair whose combined height exceeds a full page still splits.
  • A non-positive widthPt resolves to the region width; the substitution check is strictly greater than zero.
  • remainingHeight() may return zero or a negative value once the cursor has overflowed. contains() treats the region boundary as inside.
  • placementsOnPage() with an out-of-range index returns an empty list.
  • This module performs no cryptographic operations and defines no FIPS-specific behavior.

Flow Layout implements NextPDF-defined placement behavior. It does not target an external layout or typography standard, so this page carries no normative citation table. The page-break strategies are NextPDF semantics; they are not implementations of CSS fragmentation properties or of any XSL-FO keep model. All dimensions are expressed in points, matching the units the Core writer consumes.

  • Measure content upstream. The engine consumes caller-supplied heights; it has no font metrics and performs no text measurement.
  • Pre-split long text or table content into multiple elements before layout. Use isBreakable() to decide which types a chunker may split.
  • Reuse one engine per page geometry. Derive variants cheaply with withStrategy() and withRegion().
  • Group output per page with placementsOnPage() when rendering page by page.
  • Layout is a single pass, linear in the element count, and retains no document tree. Results are deterministic, which suits golden-file tests.
  • For HTML-to-PDF rendering, use the Core HTML pipeline instead; this module is not an HTML or CSS engine.

This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.