parley icon indicating copy to clipboard operation
parley copied to clipboard

Vertical alignment and the concept of a "parent" font and style

Open valadaptive opened this issue 10 months ago • 12 comments

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 Layout has an explicit "root style"/"parent style". Right now, RangedStyleBuilder and TreeStyleBuilder have 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 least ResolvedStyle) 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 in Style.
  • Layout could have APIs to access its root style and its baseline sets.
  • InlineBox would allow you to specify its baseline sets. Combined with the above API, this would allow the API consumer to implement nested layout by recursively rendering Layouts inside-out, starting with the leaf nodes and turning them into InlineBoxes positioned in the higher-up Layouts.

Does this sound like a reasonable API surface?

valadaptive avatar Mar 06 '25 01:03 valadaptive

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 the InlineBox struct and then using that in baseline computation for inline boxes.
  • Vertical alignment modes other than baseline. CSS defines top, middle, bottom, text-top, text-bottom, sub, super and numerical offsets, none of which we support. I believe this could be supported by adding a vertical_align style property to the existing Style struct without radically changing the API.

nicoburns avatar Mar 07 '25 00:03 nicoburns

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:

Image

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:

Image

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 avatar Mar 07 '25 01:03 valadaptive

@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.

nicoburns avatar Mar 07 '25 01:03 nicoburns

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>

Image

valadaptive avatar Mar 07 '25 01:03 valadaptive

Ah, looks like it's just the definition of text-top and text-bottom:

Image Image

https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align

nicoburns avatar Mar 07 '25 01:03 nicoburns

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.

valadaptive avatar Mar 07 '25 02:03 valadaptive

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!

nicoburns avatar Mar 07 '25 02:03 nicoburns

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.

Image


@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.

xStrom avatar May 21 '25 17:05 xStrom

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.

valadaptive avatar May 21 '25 17:05 valadaptive

Alright, I might play around a bit with the root style business tomorrow. We'll see if I get anywhere with it.

xStrom avatar May 21 '25 17:05 xStrom

@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.

valadaptive avatar Jun 07 '25 06:06 valadaptive

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.

nicoburns avatar Sep 26 '25 18:09 nicoburns