Skip to content
getnextpdf.com

stability: Experimental

Paged-media CSS preview flags (GCPM running content, named pages, page floats)

Opt-in preview. These four CSS features are default-off. When the flag is off, the engine produces byte-identical output to a build that never knew the feature existed. Turn a feature on only when you want it, and validate the result for your documents.

The HTML renderer adds four opt-in paged-media features from the CSS Paged Media and Generated Content for Paged Media (GCPM) modules. Each is a separate flag on CssFeatureFlags. Each carries an honest fail-closed boundary: a construct the single-pass engine cannot resolve faithfully is dropped or degraded with a named diagnostic, never rendered wrong.

FeatureFlagWhat it does when on
Named strings (GCPM)runningStringsstring-set capture plus string() in @page margin boxes — running headers and footers.
Named pages (Paged Media L3)namedPagesAdvanced@page <ident>, the page: property, and :first / :left / :right / :blank — per-page margin boxes and decoration.
Running elements (GCPM)runningElementsposition: running(<ident>) plus content: element(<ident>) — replay an element’s text in a margin box.
Page floats (Page Floats L3)pageFloatsfloat: top | bottom | snap — move a box into the page’s top or bottom band.
Terminal window
composer require nextpdf/core:^3

The flags ship in the core package. The CssFeatureFlags public surface is @since 6.1.0. The engine version (Version::VERSION) is unchanged; these features are additive and default-off.

The renderer is single-pass and streaming (see ADR-001). It keeps no document tree and writes output once in document order. That constraint shapes every feature here. Each feature resolves what it can see in one forward pass and fails closed on anything that would need a second pass or a retained tree. The boundary is documented, not hidden — knowing where a feature stops is part of using it.

You enable a feature by constructing CssFeatureFlags with the flag set to true and passing it to Config. When a flag is off, the corresponding CSS is parsed and ignored exactly as an unsupported property would be, so output is byte-identical to a build without the feature.

string-set: <ident> content() records a value as the engine passes the element. A string(<ident>) reference inside an @page margin box then resolves to the most recent value seen on that page. This is the standard mechanism for a running header that tracks the current chapter or section.

Resolution is single-pass “last seen on this page”. A string() reference resolves to the last value the engine recorded before it laid out that page’s margin boxes.

Fail-closed boundary. With the flag off, string() resolves to the empty string and output stays byte-identical. A malformed string-set content list drops that one assignment pair and continues; it never aborts the render.

The page: <ident> property assigns an element to a named page context, and a matching @page <ident> rule supplies that context’s margin boxes and page decoration. The page pseudo-classes :first, :left, :right, and :blank select the first page, recto and verso pages, and intentionally blank pages.

This feature selects a named or pseudo page’s margin boxes and decoration. It does not change page geometry.

Fail-closed boundary. A named or pseudo @page rule that tries to change geometry — size, rotate, or a content-box margin that resizes the page area — fails closed with UnsupportedNamedPageException rather than silently producing a misaligned page. The pseudo-class matching channel is the first slice; broader selector cases are deferred and documented.

position: running(<ident>) removes an element from the normal flow and parks it under a name. content: element(<ident>) in a margin box then replays that element on each page. Use it when a header needs the full styled text of a heading, not just a captured string.

Fail-closed boundary. This slice replays the text of the running element only. Rich content — images, replaced elements, nested block structure — is dropped, and the engine emits an HTML_RUNNING_ELEMENT_DEGRADED diagnostic so the loss is visible, not silent. A running() element that references itself, a nested running(), or a capture that exceeds the internal budget fails closed. With the flag off, running() and element() are inert.

float: top, float: bottom, and float: snap move a box into the page’s top or bottom band in the block axis, reserving the band’s height so the surrounding text reflows around the reserved region.

float: bottom (and snap resolving to the bottom band) is the case the single-pass engine handles directly: the box is captured and placed in the page’s bottom band as the page closes. float: top degenerates to the page-top band.

Fail-closed boundary. snap in the inline axis (snap-inline) is not supported. A box that carries an unmovable side effect — for example a link annotation, whose rectangle is bound to its flow position — cannot be relocated safely, so it falls back to normal flow and the engine emits an HTML_PAGE_FLOAT_* diagnostic explaining the fallback. With the flag off, float: top | bottom | snap is treated as an unsupported value and ignored.

SymbolLocationRole
CssFeatureFlagssrc/Html/CssFeatureFlags.phpImmutable opt-in flag set; constructor takes runningStrings, namedPagesAdvanced, runningElements, pageFloats (all default false).
Config::withCssFeatureFlags(CssFeatureFlags $flags): selfsrc/Core/Config.phpAttaches the flag set to a document configuration.
CssFeatureFlags::forMode(CssRenderingMode $mode, ?self $explicit = null): selfsrc/Html/CssFeatureFlags.phpResolves a flag set for a rendering mode (Safe mode forces every flag off; Normal mode uses the explicit set, or allEnabled() when none is supplied).
UnsupportedNamedPageExceptionsrc/Html/PagedMedia/UnsupportedNamedPageException.phpThrown when a named/pseudo @page rule changes page geometry.

Diagnostic warning codes surface through the render result’s advisory channel: HTML_RUNNING_ELEMENT_DEGRADED, the HTML_RUNNING_ELEMENT_* family, and the HTML_PAGE_FLOAT_* family.

Enable named strings for a running header that tracks the current chapter.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
use NextPDF\Html\Css\CssFeatureFlags;
$config = (new Config())->withCssFeatureFlags(
new CssFeatureFlags(runningStrings: true),
);
$doc = Document::createStandalone($config);
$doc->addPage();
$doc->writeHtml(
'<style>'
. 'h2 { string-set: chapter content(); }'
. '@page { @top-center { content: string(chapter); } }'
. '</style>'
. '<h2>Introduction</h2><p>Body text…</p>',
);
$doc->save(__DIR__ . '/running-header.pdf');

Enable several flags together, and treat the advisory channel as a signal that a construct degraded. The flags are independent; turn on only the ones you use.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
use NextPDF\Exception\UnsupportedNamedPageException;
use NextPDF\Html\Css\CssFeatureFlags;
$config = (new Config())->withCssFeatureFlags(new CssFeatureFlags(
runningStrings: true,
namedPagesAdvanced: true,
runningElements: true,
pageFloats: true,
));
$doc = Document::createStandalone($config);
$doc->addPage();
try {
$doc->writeHtml($html);
} catch (UnsupportedNamedPageException $e) {
// A named @page rule tried to change page geometry (size/rotate/margin).
// The engine fails closed rather than emit a misaligned page.
throw $e;
}
$doc->save($out);
// Inspect $doc's advisory channel for HTML_RUNNING_ELEMENT_DEGRADED and
// HTML_PAGE_FLOAT_* before treating the output as final.
  • All four flags are independent and default-off. An off flag yields byte-identical output. Enable only what you use.
  • string() is empty when runningStrings is off, by design. There is no warning for the off case; it is the documented default.
  • Running elements replay text only. Images and nested blocks inside a running element are dropped with HTML_RUNNING_ELEMENT_DEGRADED. Check the advisory channel.
  • Named pages cannot change geometry. A geometry-changing named/pseudo @page rule throws UnsupportedNamedPageException. Set page size and rotation through Config, not through a named @page rule.
  • Page floats keep links in flow. A floated box that contains a link annotation falls back to normal flow with an HTML_PAGE_FLOAT_* diagnostic, because the link rectangle is bound to its flow position.

Each feature adds a bounded amount of single-pass work: named strings record one value per string-set element; named pages add a per-page margin-box resolution; running elements capture one text buffer per parked element; page floats reserve one band per page. None retains a document tree, so the O(nesting depth) memory model of the streaming renderer is preserved. The per-page performance_budget (wall_ms: 1500, peak_mb: 64) is unchanged.

These flags do not widen the input surface. The HTML security policy, the CSS property allowlist, and the stylesheet-byte and nesting caps apply unchanged. Captured string and element content is escaped through the same output path as any other text. The features add layout behavior, not a new ingestion channel.

StatementSpecClause
string-set records a named string; string() resolves it in a page margin box.W3C CSS Generated Content for Paged Media§3
position: running() removes an element from flow; content: element() replays it.W3C CSS Generated Content for Paged Media§5
The page property and @page <ident> select a named page context.W3C CSS Paged Media Module Level 3§3
float: top | bottom | snap floats a box in the block axis to a page band.W3C CSS Page Floats Level 3§5

These are preview implementations of working-group module features. NextPDF implements a single-pass subset with the documented fail-closed boundaries above. Per-property verified status is tracked in the CSS support matrix; no end-to-end conformance is claimed here. No standards text is reproduced.