This directory contains the inline layout implementation of Blink's new layout engine “LayoutNG”.
This README can be viewed in formatted form here.
Other parts of LayoutNG is explained here.
Inline layout is one of CSS normal flow layout models.
From the CSS2 spec on inline formatting context: an inline formatting context is established by a block container box that contains no block-level boxes.
Following DOM tree is transformed to fragment tree as in the following.
Inline layout is performed in the following phases:
This is similar to CSS Text Processing Order of Operations, but not exactly the same, because the spec prioritizes the simple description than being accurate.
For inline layout there is a pre-layout pass that prepares the internal data structures needed to perform line layout.
The pre-layout pass, triggered by calling NGInlineNode::PrepareLayout()
, has three separate steps or stages that are executed in order:
CollectInlines
: Performs a depth-first scan of the container collecting all non-atomic inlines and TextNodes
s. Atomic inlines are represented as a unicode object replacement character but are otherwise skipped. Each non-atomic inline and TextNodes
is fed to a NGInlineItemsBuilder instance which collects the text content for all non-atomic inlines in the container.
During this process white-space is collapsed and normalized according to CSS white-space processing rules.
The CSS text-transform is already applied in LayoutObject tree. The plan is to implement in this phase when LayoutNG builds the tree from DOM.
SegmentText
: Performs BiDi segmentation and resolution. See Bidirectional text below.
ShapeText
: Shapes the resolved BiDi runs using HarfBuzz. TODO(eae): Fill out
NGLineBreaker takes a list of NGInlineItem, measure them, break into lines, and produces a list of NGInlineItemResult for each line.
NGInlineItemResult keeps several information needed during the line box construction, such as inline size and ShapeResult, though the inline position is recomputed later because Bidirectional text may change it.
This phase:
ShapingLineBreaker is... TODO(kojii): fill out.
NGInlineLayoutAlgorithm::CreateLine()
takes a list of NGInlineItemResult and produces NGPhysicalLineBoxFragment for each line.
Lines are then wrapped in an anonymous NGPhysicalBoxFragment so that one NGInlineNode has one corresponding fragment.
This phase consists of following sub-phases:
Bidirectional reordering: Reorder the list of NGInlineItemResult according to UAX#9 Reordering Resolved Levels. See Bidirectional text below.
After this point forward, the list of NGInlineItemResult is in visual order; which is from line-left to line-right. The block direction is still logical, but the inline direction is physical.
Create a NGPhysicalFragment for each NGInlineItemResult in visual (line-left to line-right) order, and place them into NGPhysicalLineBoxFragment.
The inline size of each item was already determined by NGLineBreaker, but the inline position is recomputed because BiDi reordering may have changed it.
In block direction, NGPhysicalFragment is placed as if the baseline is at 0. This is adjusted later, possibly multiple times. See Inline Box Tree and the post-process below.
Post-process the constructed line box. This includes:
A flat list structure is suitable for many inline operations, but some operations require an inline box tree structure. A stack of NGInlineBoxState is constructed from a list of NGInlineItemResult to represent the box tree structure.
This stack:
Because of index-based operations to the list of NGInlineItemResult, the list is append-only during the process. When all operations are done, OnEndPlaceItems()
turns the list into the final fragment tree structure.
Not all inline-level boxes produces NGPhysicalBoxFragments.
NGInlineLayoutAlgorithm determines whether a NGPhysicalBoxFragment is needed or not, such as when a <span>
has borders, and calls NGInlineBoxState::SetNeedsBoxFragment()
.
Since NGPhysicalBoxFragment needs to know its children and size before creating it, NGInlineLayoutStateStack::AddBoxFragmentPlaceholder()
first creates placeholders. We then add children, and adjust positions both horizontally and vertically.
Once all children and their positions and sizes are finalized, NGInlineLayoutStateStack::CreateBoxFragments()
creates NGPhysicalBoxFragment and add children to it.
Computing baselines in LayoutNG goes the following process.
::AddBaselineRequest()
.::Layout()
, that calls appropriate layout algorithm.::Baseline()
, or by higher level functions such as NGBoxFragment::BaselineMetrics()
.Algorithms are responsible for checking NGConstraintSpace::BaselineRequests()
, computing requested baselines, and calling NGBoxFragmentBuilder::AddBaseline()
to add them to NGPhysicalBoxFragment.
NGBaselineRequest consists of NGBaselineAlgorithmType and FontBaseline.
In most normal cases, algorithms should decide which box should provide the baseline for the specified NGBaselineAlgorithmType and delegate to it.
FontBaseline currently has only two baseline types, alphabetic and ideographic, and they are hard coded to derive from the CSS writing-mode property today.
We plan to support more baseline types, and allow authors to specify the baseline type, as defined in the CSS dominant-baseline property in future. NGPhysicalLineBoxFragment and NGPhysicalTextFragment should be responsible for computing different baseline types from font metrics.
UAX#9 Unicode Bidirectional Algorithm defines processing algorithm for bidirectional text.
The core logic is implemented in NGBidiParagraph, which is a thin wrapper for ICU BiDi.
In a bird's‐eye view, it consists of two parts:
Before line breaking: Segmenting text and resolve embedding levels as defined in UAX#9 Resolving Embedding Levels.
The core logic uses ICU BiDi ubidi_getLogicalRun()
function.
This is part of the Pre-layout phase above.
After line breaking: Reorder text as defined in UAX#9 Reordering Resolved Levels.
The core logic uses ICU BiDi ubidi_reorderVisual()
function.
This is part of the Line Box Construction phase above.