stability: Experimental
Paged-media CSS preview flags (GCPM running content, named pages, page floats)
At a glance
Section titled “At a glance”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.
| Feature | Flag | What it does when on |
|---|---|---|
| Named strings (GCPM) | runningStrings | string-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) | runningElements | position: running(<ident>) plus content: element(<ident>) — replay an element’s text in a margin box. |
| Page floats (Page Floats L3) | pageFloats | float: top | bottom | snap — move a box into the page’s top or bottom band. |
Install
Section titled “Install”composer require nextpdf/core:^3The 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.
Conceptual overview
Section titled “Conceptual overview”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.
Named strings — runningStrings
Section titled “Named strings — runningStrings”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.
Named pages — namedPagesAdvanced
Section titled “Named pages — namedPagesAdvanced”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.
Running elements — runningElements
Section titled “Running elements — runningElements”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.
Page floats — pageFloats
Section titled “Page floats — pageFloats”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.
API surface
Section titled “API surface”| Symbol | Location | Role |
|---|---|---|
CssFeatureFlags | src/Html/CssFeatureFlags.php | Immutable opt-in flag set; constructor takes runningStrings, namedPagesAdvanced, runningElements, pageFloats (all default false). |
Config::withCssFeatureFlags(CssFeatureFlags $flags): self | src/Core/Config.php | Attaches the flag set to a document configuration. |
CssFeatureFlags::forMode(CssRenderingMode $mode, ?self $explicit = null): self | src/Html/CssFeatureFlags.php | Resolves 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). |
UnsupportedNamedPageException | src/Html/PagedMedia/UnsupportedNamedPageException.php | Thrown 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.
Code sample — Quick start
Section titled “Code sample — Quick start”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');Code sample — Production
Section titled “Code sample — Production”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.Edge cases & gotchas
Section titled “Edge cases & gotchas”- All four flags are independent and default-off. An off flag yields byte-identical output. Enable only what you use.
string()is empty whenrunningStringsis 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
@pagerule throwsUnsupportedNamedPageException. Set page size and rotation throughConfig, not through a named@pagerule. - 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.
Performance
Section titled “Performance”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.
Security notes
Section titled “Security notes”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.
Conformance
Section titled “Conformance”| Statement | Spec | Clause |
|---|---|---|
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.