Pro edition
Flow Layout — Deep Reference
At a glance
Section titled “At a glance”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.
Availability & licensing
Section titled “Availability & licensing”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.
Public API surface
Section titled “Public API surface”All symbols live in the NextPDF\Pro\FlowLayout namespace. All value objects are final and immutable.
| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
StreamingLayoutEngine::__construct | LayoutRegion $region, PageBreakStrategy $strategy = PageBreakStrategy::Greedy | Binds a per-page content area to a break strategy | StreamingLayoutEngine | — | Strategy defaults to Greedy. |
StreamingLayoutEngine::layout | list<FlowElement> $elements | Single forward pass; sequential placement with strategy-driven page breaks | LayoutResult | Never throws | An empty list yields one empty page. |
StreamingLayoutEngine::withStrategy | PageBreakStrategy $strategy | Derives a new engine with the same region | self | — | The receiver is unchanged. |
StreamingLayoutEngine::withRegion | LayoutRegion $region | Derives a new engine with the same strategy | self | — | The receiver is unchanged. |
FlowElement::__construct | FlowElementType $type, string $content, float $widthPt = 0, float $heightPt = 0, float $marginTopPt = 0, float $marginBottomPt = 0, bool $keepWithNext = false | Immutable element value object | FlowElement | — | Only construction path for Table elements. |
FlowElement::text | string $content, float $height | Text element with a caller-measured height | self (static) | — | Width 0 resolves to the region width at placement. |
FlowElement::image | string $path, float $width, float $height | Image element; content carries the path | self (static) | — | The engine never opens the file. |
FlowElement::spacer | float $height | Vertical whitespace with empty content | self (static) | — | — |
FlowElement::pageBreak | — | Explicit break marker | self (static) | — | Emits no PlacedElement. |
FlowElement::totalHeight | — | Height plus top and bottom margins | float | — | All fit checks use this value. |
FlowElementType | enum cases Text, Image, Table, Spacer, PageBreak | String-backed: text, image, table, spacer, page_break | — | — | — |
FlowElementType::isBreakable | — | Text and Table return true; others return false | bool | — | Classification only; see the atomic-placement contract below. |
LayoutRegion::__construct | float $x, float $y, float $width, float $height | Top-left-origin content box, measured in points | LayoutRegion | — | No validation; values are taken as given. |
LayoutRegion::contains | float $px, float $py | Boundary-inclusive point-in-region test | bool | — | — |
LayoutRegion::remainingHeight | float $currentY | Region height minus the consumed vertical offset | float | — | Zero or negative once the cursor has overflowed. |
LayoutResult::__construct | list<PlacedElement> $placements, int $pageCount, float $totalHeightPt | Immutable layout outcome | LayoutResult | — | — |
LayoutResult::placementsOnPage | int $pageIndex | Filters placements by zero-based page index | list<PlacedElement> | — | The returned list is re-indexed. |
LayoutResult::isEmpty | — | True when no elements were placed | bool | — | True for empty and break-only input. |
PageBreakStrategy | enum cases Greedy, AvoidOrphans, KeepTogether | String-backed: greedy, avoid_orphans, keep_together | — | — | — |
PageBreakStrategy::label | — | Human-readable strategy label | string | — | — |
PlacedElement::__construct | FlowElement $element, int $pageIndex, float $x, float $y, float $width, float $height | Immutable placement record | PlacedElement | — | Coordinates are in points, top-left origin. |
public function layout(array $elements): LayoutResultpublic function withStrategy(PageBreakStrategy $strategy): selfpublic function withRegion(LayoutRegion $region): selfpublic static function text(string $content, float $height): selfpublic static function image(string $path, float $width, float $height): selfpublic static function spacer(float $height): selfpublic static function pageBreak(): selfBehavior contract
Section titled “Behavior contract”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:
xis the region’s left edge.yis the current cursor position plus the element’s top margin.widthis the element’swidthPtwhen positive, otherwise the region width.heightis the element’sheightPt, 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
PageBreakelement 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. Greedyadds no further condition: fitting elements are always placed.AvoidOrphansbreaks 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.KeepTogetherbreaks before a fitting element when itskeepWithNextflag is set, a next element exists, the cursor is not at the page top, and the combinedtotalHeight()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.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- 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
PageBreakplaces the first content element on page index 1, giving a page count of at least 2. - Consecutive
PageBreakelements each advance the page counter, producing blank pages. A trailing one leaves a final empty page inpageCount. - 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
widthPtresolves 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.
Conformance
Section titled “Conformance”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.
Development notes
Section titled “Development notes”- 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()andwithRegion(). - 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.
Publication boundary
Section titled “Publication boundary”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.