Vertical alignment and the concept of a "parent" font and style
There was some discussion in https://github.com/linebender/parley/issues/25 about 1) vertical alignment of inline boxes and 2) a tree-based vs. flat style structure. That discussion seemed to show a preference for a tree-based API, but https://github.com/linebender/parley/issues/279 suggests that the layout algorithm will eventually want to operate on a flat list of style spans.
I need to implement some sort of vertical alignment for inline boxes, and ran into an API design issue when looking at the way CSS does it (and this library seems to have the goal of matching CSS' API).
I still don't fully understand the way that the CSS inline layout algorithm works, but vertical alignment of an "inline-level box" (in Parley's case, either an InlineBox or a span of text) is done relative to the baselines of its "parent". See 4. Baseline Alignment (emphasis mine):
While most CSS formatting contexts position content by aligning boxes with respect to their container’s edges, inline layout positions boxes in the block axis by aligning them with respect to each other using their baselines.
More specifically, (unless using a line-relative shift value) each glyph or inline-level box is aligned in the block axis by positioning its alignment baseline to match the corresponding baseline of its parent (which is its alignment context), and then is potentially shifted from that position according to its post-alignment shift.
When aligning a box, the alignment baseline is chosen according to its alignment-baseline and baseline-source values (see shorthand vertical-align), and defaults to matching the parent’s dominant-baseline. For a glyph, the alignment baseline is always determined by the parent’s dominant baseline.
When aligning inline boxes with other inline boxes, their baseline sets are also based on inherited font settings:
The first/last baseline set of a line box is generated from the dominant baseline and the font settings of its root inline box.
If my mental model of CSS is correct, then a workable API for Parley that would allow implementing HTML/CSS layout on top of it would be:
- A
Layouthas an explicit "root style"/"parent style". Right now,RangedStyleBuilderandTreeStyleBuilderhave a default and root style respectively, but they're conceptually treated the same as any other style node, when they need to be treated specially for the purpose of doing layout.- Beyond this, I think it's OK to use a flattened set of styled spans for everything else.
- We can then use this "root style" to get the font metrics / baseline set for alignment purposes.
- This implies that
Style(or at leastResolvedStyle) should have an API for fetching font metrics (if only for use internally). Right now, there doesn't seem to be a way to get them until shaping actually occurs. They depend on font variations, but those will probably also be specified inStyle.
- This implies that
Layoutcould have APIs to access its root style and its baseline sets.InlineBoxwould allow you to specify its baseline sets. Combined with the above API, this would allow the API consumer to implement nested layout by recursively renderingLayouts inside-out, starting with the leaf nodes and turning them intoInlineBoxes positioned in the higher-upLayouts.
Does this sound like a reasonable API surface?
This isn't a complete answer to your questions but:
- I believe the "parent" in this context is actually the line box.
- Parley already calculates a baseline for each line. The actual computation might need tweaking, but the basic feature is there.
- Do you actually need support for baseline sets? (i.e. to differentiate between different kinds of dominant baseline (alphabetic, ideographic, mathematical, etc)). We'll definitely need to support this eventually, but I had this down as a "worry about it later" item.
In my mind the most pressing features that are missing are:
- Baseline alignment for inline boxes. Parley currently assumes that the baseline of an inline box is the bottom of the box. This is correct for replaced content like images but not for nested textual content. This can be fairly easily addressed by adding a
baseline: Option<f32>field to theInlineBoxstruct and then using that in baseline computation for inline boxes. - Vertical alignment modes other than
baseline. CSS definestop,middle,bottom,text-top,text-bottom,sub,superand numerical offsets, none of which we support. I believe this could be supported by adding avertical_alignstyle property to the existing Style struct without radically changing the API.
I believe the "parent" in this context is actually the line box.
I was under the impression that a "line box" is a single laid-out line within a larger inline context:
It does seem that the "root inline box" is not the parent container, but for the purposes of style inheritance, behaves like it:
The root inline box inherits from its parent block container, but is otherwise unstyleable.
I don't need support for baseline sets at the moment. It should fall pretty naturally out of support for vertical alignment modes.
The reason I think a "parent" style is necessary is because it seems like the only real way to handle choosing which metrics to use for a given line of text that has mixed styles. For example, take this HTML:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<style>
.lg {
font-size: 200%;
}
.sm {
font-size: 50%;
}
</style>
</head>
<body>
<p style="font-size: 200%">
<span class="sm">a</span><span class="sm" style="vertical-align: text-top;">b</span><span class="sm" style="vertical-align: text-bottom;">c</span>
<br>
<span>a</span><span style="vertical-align: text-top;">b</span><span
style="vertical-align: text-bottom;">c</span>
</p>
<p>
<span>a</span><span style="vertical-align: text-top;">b</span><span style="vertical-align: text-bottom;">c</span>
<br>
<span class="lg">a</span><span class="lg" style="vertical-align: text-top;">b</span><span class="lg" style="vertical-align: text-bottom;">c</span>
</p>
</body>
These two <p> elements have different font sizes, but their child elements adjust their font sizes to compensate. If the baseline set of a line box was determined only by the style of its direct contents, we'd expect the two <p> elements to appear identical, but they don't:
I actually tried implementing vertical alignment modes without a parent style, but discovered that there's no way to do it that gives the same result as CSS.
@valadaptive I think this may be because of the rule that "the line-height for an inline span is floored by the line-height of the block container". So for the smaller text (top "abc" in each paragraph), the line-height will be larger in the first example. Blitz is currently accounting for this when setting the line-height property for each inline span so that Parley doesn't need to know about it.
OK, it looks like line-height does affect the text-top and text-bottom baselines, but... not solely? Unless I'm missing something.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<style>
.lg {
font-size: 200%;
}
.sm {
font-size: 50%;
}
</style>
</head>
<body>
<p style="font-size: 200%; line-height: 50px">
<span class="sm">a</span><span class="sm" style="vertical-align: text-top;">b</span><span class="sm" style="vertical-align: text-bottom;">c</span>
<br>
<span>a</span><span style="vertical-align: text-top;">b</span><span
style="vertical-align: text-bottom;">c</span>
</p>
<p style="line-height: 50px">
<span>a</span><span style="vertical-align: text-top;">b</span><span style="vertical-align: text-bottom;">c</span>
<br>
<span class="lg">a</span><span class="lg" style="vertical-align: text-top;">b</span><span class="lg" style="vertical-align: text-bottom;">c</span>
</p>
</body>
Ah, looks like it's just the definition of text-top and text-bottom:
https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
sub, super, and middle also depend on the parent element's font. Sorry if I'm not explaining myself well, but I'm still convinced there needs to be a "parent" style of some sort--I got much of the way through implementing vertical alignment before realizing it (I can put up a draft PR).
top, bottom, and center (not sure if that last one is implemented anywhere) are calculated in a second pass, after the bounds of the line box have been calculated without including any items with those vertical alignment values. This is because they're relative to the bounds of the line box itself, not to any font metrics.
Sorry if I'm not explaining myself well, but I'm still convinced there needs to be a "parent" style of some sort--I got much of the way through implementing vertical alignment before realizing it (I can put up a draft PR).
Yeah, I think I'm now in agreement. I guess the API of the builders probably doesn't need to change. We just need to store the root style into the layout. If we later get rid of the builders I guess we'd need an explicit public API.
It would be great to see a draft PR!
It is my understanding as well that we will need a root style. For example in the following image all three elements have explicitly low font size and line height, but the line's baseline, the selection box height, and the top/bottom vertical alignments are based on the container style.
@valadaptive have you started any work on adding support for root style? I'm interested in getting vertical alignment working as well, but I wouldn't want to do concurrent work.
I haven't really looked into vertical alignment recently. I have an old draft PR, but it was based on a misunderstanding of the CSS spec and didn't get very far anyway.
Alright, I might play around a bit with the root style business tomorrow. We'll see if I get anywhere with it.
@xStrom Did you get very far? At some point I need to implement vertical alignment for egui stuff but don't want to step on your toes.
This example (https://drafts.csswg.org/css-inline-3/#example-713d3335) suggests that the "parent" can be an inline span as well as the root-level container.