Skip to content
getnextpdf.com

stability: Experimental

CJK vertical writing support

Opt-in preview. The vertical composer is default-off. When it is off, the engine renders horizontally exactly as before — byte-identical. Turn it on only for the documents that need real vertical lines, and validate the result.

The HTML renderer adds a real vertical line composer for the CSS writing modes writing-mode: vertical-lr and writing-mode: vertical-rl. When the composer is on, glyphs stack top to bottom with per-glyph placement taken from the font’s real vertical metrics (the vhea and vmtx tables), as the PDF vertical-writing model in ISO 32000-2 §9.7.5 describes. Both vertical block-flow directions are supported.

Terminal window
composer require nextpdf/core:^3

The composer ships in the core package. The CssFeatureFlags::$layoutVerticalComposer opt-in is @since 6.1.0. The engine version is unchanged; the feature is additive and default-off.

Vertical composition turns on only when both layoutVerticalLr and layoutVerticalComposer are set. With them on, a vertical-lr or vertical-rl run composes as a real vertical line: each glyph is placed by its vertical advance from the font’s vhea/vmtx metrics, and glyphs that UAX #50 marks upright are kept upright. vertical-lr lays columns left to right; vertical-rl lays columns right to left.

This differs from the cmap-aware encoding facade documented in Set CJK text with cmap-aware encoding, which proves the encoding path but does not itself drive a vertical writing mode. This page documents the layout-side composer that the writing-mode opt-in enables.

Fail-closed boundary — when it composes, and what it does otherwise

Section titled “Fail-closed boundary — when it composes, and what it does otherwise”

The composer is conservative by design. It composes a run vertically only when every glyph in the run is UAX #50 upright with real vertical metrics, there is no open link inside the run, and the run is a single column. When any of those does not hold — the flag is off, or a run cannot be composed faithfully — the engine falls back to horizontal layout and emits a mode-matching deferred diagnostic:

  • HTML_WRITING_MODE_LR_DEFERRED for a vertical-lr run that could not compose.
  • HTML_WRITING_MODE_RL_DEFERRED for a vertical-rl run that could not compose.

Each diagnostic carries a reason, so a deferral is observable and explainable, never a silent horizontal render of text the author asked to set vertically.

These cases are out of scope for the current slice and are tracked for later work:

  • Rotated (non-upright) glyphs inside a vertical run.
  • Multi-column vertical wrapping.
  • Vertical link rectangles (a link inside a vertical run defers the run).
SymbolLocationRole
CssFeatureFlags::$layoutVerticalComposersrc/Html/CssFeatureFlags.phpOpt-in flag for the vertical line composer (default false).
CssFeatureFlags::$layoutVerticalLrsrc/Html/CssFeatureFlags.phpGate for vertical-lr; both must be on to compose.
Config::withCssFeatureFlags(CssFeatureFlags $flags): selfsrc/Core/Config.phpAttaches the flag set to a document configuration.

The deferred-diagnostic codes HTML_WRITING_MODE_LR_DEFERRED and HTML_WRITING_MODE_RL_DEFERRED surface through the render result’s advisory channel.

<?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(
layoutVerticalLr: true,
layoutVerticalComposer: true,
));
$doc = Document::createStandalone($config);
$doc->addPage();
$doc->writeHtml(
'<div style="writing-mode: vertical-rl; font-family: NotoSerifJP;">'
. '日本語の縦書き'
. '</div>',
);
$doc->save(__DIR__ . '/vertical.pdf');

A run that cannot compose faithfully renders horizontally and adds an HTML_WRITING_MODE_RL_DEFERRED advisory with a reason. Inspect the advisory channel before treating vertical output as final.

Register a font that carries real vertical metrics through DocumentFactory, so the composer can read vhea/vmtx, then opt the document into the composer.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Html\Css\CssFeatureFlags;
use NextPDF\Typography\FontRegistry;
$fontRegistry = new FontRegistry();
$fontRegistry->register('/path/to/NotoSerifJP-Regular.otf', alias: 'NotoSerifJP');
$config = (new Config())->withCssFeatureFlags(new CssFeatureFlags(
layoutVerticalLr: true,
layoutVerticalComposer: true,
));
$factory = new DocumentFactory($fontRegistry, new ImageRegistry(maxCacheBytes: 0));
$doc = $factory->create($config);
$doc->setLanguage('ja');
$doc->addPage();
$doc->writeHtml(
'<div style="writing-mode: vertical-rl; font-family: NotoSerifJP;">'
. '縦書きの本文。'
. '</div>',
);
$doc->save($out);
  • Both flags are required. The composer needs layoutVerticalLr and layoutVerticalComposer. With either off, the run renders horizontally.
  • Real vertical metrics are required. A font without vhea/vmtx cannot drive the composer; the run defers to horizontal layout.
  • Deferral is observable. A run that cannot compose emits HTML_WRITING_MODE_LR_DEFERRED / HTML_WRITING_MODE_RL_DEFERRED with a reason. It never silently renders sideways.
  • No conformance claim from this path. Vertical composition is a layout capability; it is not a PDF/UA-2 or PDF/A-4 conformance statement for the produced file. A checker decides conformance.

Composition adds a per-glyph vertical-advance lookup over the run, linear in glyph count. The budget (wall_ms: 2000, peak_mb: 128) follows the CJK profile, because vertical-metrics fonts are large and the dominant cost is font handling, not the composition pass.

The composer reads vertical metrics from already-registered, already-validated fonts. It does not open a new input channel. Font files remain untrusted binary input handled by the typography layer’s existing validation. Composed text is rendered, not interpreted.

StatementSpecClause
Vertical writing uses CIDFont vertical glyph metrics for placement.ISO 32000-2§9.7.5
writing-mode: vertical-lr / vertical-rl set the block-flow direction.W3C CSS Writing Modes Level 3§3
Per-glyph upright orientation follows the Unicode vertical-orientation property.Unicode UAX #50Vertical Orientation

This is a preview implementation of a single-column upright vertical subset with the documented fail-closed boundaries above. NextPDF does not assert that output from this path conforms to any profile; a checker makes that determination. No standards text is reproduced.