Vertical layout metrics — verticalFlowMetrics

Reads out the values the engine actually used for a vertical
(writing-mode: vertical-rl) multi-column block, from page JavaScript.
You get column boundaries, stride, line count, and how much each line really
occupied — without inferring anything from coordinates.

Ebook viewers use this to answer "which line are we on" and "where does the
next column start". Previously the only option was to line up
getBoundingClientRect() results and guess, which stopped matching as soon as
hanging punctuation or a column break was involved.


Using it

const m = window.__blinkgtk?.verticalFlowMetrics(document.getElementById('book'));
if (m) {
  console.log(m.columnCount, m.stride, m.originX);
}

The argument is the vertical multi-column container element. When it does
not apply (not a vertical multi-column block, layout not yet resolved, and so
on) it returns null rather than throwing, so you do not need try/catch.

Enabling it

This API is not present by default. It requires a startup flag:

--enable-blink-features=CJKVerticalColumnFragmentation

Without the flag, window.__blinkgtk itself does not exist. Feature-detect
with ?. or typeof, as above; calling it blindly where it is absent raises a
TypeError.

What you get

All values are in content px — layout coordinates, before page zoom.

Name Type Meaning
stride number Advance per column: used column width + used column gap
originX number Right edge X of the first line (start of the rightmost column)
columnCount number Number of columns
lineCount number Total number of body lines
contentWidth number Total block-direction size of the multi-column block
hasBoundaries boolean Whether boundaries can be derived (stride > 0 and columnCount > 0)
boundaries null See below. Currently always null
generation number Layout generation. Unchanged means no recomputation is needed
lines Array Per-line measurements (below)

Column boundaries

boundaries is currently always null. CSS multi-column layout is
structurally evenly spaced (same used column width, same used gap), so an array
would carry no extra information. Derive boundaries by arithmetic:

// Right edge X of column n (0-based, from the right)
const rightEdge = n => m.originX - m.stride * n;

If a form with uneven spacing (for example column-span) ever needs to be
supported, a measured array will be placed here then. Check hasBoundaries
before relying on it.

Each entry of lines

Name Type Meaning
inlineSize number What the line actually occupied — not the space available to it
blockOffset number Block-direction position of the line (X in vertical writing)
columnIndex number Which column the line belongs to. The engine's own value, not a coordinate comparison
hasHanging boolean Whether the line ends with hanging punctuation. The amount is not retained, only the flag

The point is that inlineSize is what was occupied. Comparing it against
contentWidth tells you whether a line filled its column.

Real values

Measured against the shipped build (captured 2026-09-03, Chromium 152.0.7977.64): a 900x640 window,
column-width: 11em, font: 16px/1.75 serif, column-gap: 2em.

{
  "stride": 272,
  "originX": 352,
  "columnCount": 2,
  "lineCount": 24,
  "contentWidth": 544,
  "hasBoundaries": true,
  "boundaries": null,
  "generation": 2,
  "lines": [
    { "blockOffset": 324, "columnIndex": 0, "hasHanging": false, "inlineSize": 224 },
    { "blockOffset": 296, "columnIndex": 0, "hasHanging": false, "inlineSize": 240 }
  ]
}

Reading it: two columns, stride 272px. The rightmost column starts at X=352, so
the second is at 352 - 272 = 80. There are 24 lines; the first two are both in
column 0. Line 1 occupied 224px, line 2 occupied 240px.

Things to watch

See also