[csswg-drafts] [css-inline-3] Initial-letters layout can be improved (#5015)

faceless2 has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-inline-3] Initial-letters layout can be improved ==
The `initial-letters` property is defined to take two values - the first controls the size of the initial letter, the second how much the initial letter is moved up or down: its _shift_. The shift is relative to an initial alignment set by by `initial-letters-align`.

I want to demonstrate first that the _shift_ value and the `initial-letters-align` property are unnecessary, as the same results can be achieved with the regular `baseline-shift` and `alignment-baseline` properties. And second, with hanging baselines, the current `initial-letters` spec will give incorrect results.

As currently defined:

1. Initial letters are inline elements. Their font size is fixed and determinable before any layout takes place. They do not contribute to the line-height calculation.

2. The initial letter is aligned with reference to both "over" and "under"  alignment points, which are specified by `initial-letters-align`.

### Initial-letters shift is not required

If you have a fixed font size and two alignment points, you're overconstrained. One of these properties must be derived or ignored. The spec states it's the "under" alignment point that matters; the "over" alignment is not part of the block-axis layout algorithm. The "under" alignment point is [positioned](https://drafts.csswg.org/css-inline-3/#initial-letter-block-position) against a hypothetical position:

> ... the initial letter is positioned as required to satisfy its under alignment point (initial-letters-align) at its specified sink (initial-letters), i.e. it is positioned such that it would sink the number of lines specified by initial-letters’s second argument and align to the requisite under alignment point if it was assumed that its containing block held only the initial letter itself followed by an infinite sequence of plain text as the direct contents of its root inline box.

You can restate that paragraph *exactly* as

> ... the initial letter is positioned as required to satisfy its under alignment point on the _first line_, then it is shifted down by  `([initial-letters's second argument] - 1) * line-height`.

 As our initial-letter doesn't contribute to the line-height calculation, the shift required by `initial-letters: 3 3` in the images below could equally be achieved by `baseline-shift: -2lh`. The _actual_ height of any lines after the first is irrelevant.

![image](https://user-images.githubusercontent.com/989243/80130295-42292900-8590-11ea-8b71-fe652262ea5a.png)

![image](https://user-images.githubusercontent.com/989243/80130389-66850580-8590-11ea-8ba1-5ed8cb706bae.png)

Furthermore, we've just aligned the initial letter against the alphabetic baseline of the first line - in this example, where it would be aligned anyway, thanks to the default values of `alignment-baseline` and `dominant-baseline`.


### Non-alphabetic baselines don't always work.

With `initial-letters-align: hanging`, our "under" alignment point is still the alphabetic baseline of a hypothetical third line (remember the "over" point is unused). How does that work with `initial-letters: 3 3`?

![image](https://user-images.githubusercontent.com/989243/80132381-80741780-8593-11ea-99e4-9cd317fff3b9.png)

It works very well. Note we can still simply position against the alphabetic baseline of the first line then shift down by `2lh`.

But, if we change the initial-letter to `initial-letters: 2.7 3` things don't look so good.

![image](https://user-images.githubusercontent.com/989243/80132591-d3e66580-8593-11ea-8de0-6df0d080c1fa.png)

Note the hanging baseline of the initial letter no longer matches the rest of the paragraph. How do we fix this? We can't. The "under" alignment point is what matters, and it's an integer value. The bottom of the initial letter must align with the bottom of the "ABC"

_(note: the limitations of integer values for the shift was also raised by @zed-vector in https://github.com/w3c/csswg-drafts/issues/4171#issuecomment-590535207; it's not specific to hanging baselines - if you want top-alignment, you have to use an integer multiplier for size)_

The solution here is to forget about `initial-letters-align`completely. Just set `dominant-baseline:hanging` on the paragraph, then align our initial letter as a regular inline. Exactly as we could have done for the alphabetic baseline examples above:
![image](https://user-images.githubusercontent.com/989243/80133778-a995a780-8595-11ea-9e07-e8972c51e3e5.png)

Vertical alignment of inline boxes on a line is a very well understood algorithm. Once you redefine `initial-letters` block-axis alignment to use the same algorithm, it becomes simpler to specify, implement and test. And you can redefine it this way, easily, once you drop the pretence that there are two alignment points.

Here's a quick comparison of the two approaches for aligning initial-letters to their linebox.

Special Initial-letters-align algorithm
* "under" alignment point of "initial-letter" is either derived from its text content - ideographic baseline for Han, Hangul, Kana, or Yi, alphabetic baseline otherwise - or set to the bottom of the border-box. It cannot be set by the author.
* alignment point of linebox is specified by the `initial-letters-align` property, of which the "under" alignment points are either alphabetic or ideographic. Proposed user-agent defaults are recommended based on language.
* images can be bottom-aligned with `initial-letters-align: border-box`.
* top-alignment is possible, but only when the `initial-letters` size is an integer.

Regular inline alignment
* alignment point of "initial letter" is specified by the `alignment-baseline` property, which is also the case for the rest of the paragraph. The default, "baseline", does the right thing if dominant baseline is set.
* alignment point of linebox is specified by the `dominant baseline` of the paragraph. Anyone wanting hanging, ideographic or other baselines would need to set this, as there is no user-agent default based on language.
* image alignment, and alignment of entire subtrees, is well defined. It's easy to align images to the top or bottom of the line with `alignment-baseline: top` or `alignment-baseline: bottom`
* top-alignment is always possible.


### Enough already, skip to the end

Here's my suggestion:

1. Either get rid of the second value of the "initial-letters" property, or make it a shorthand to set  `baseline-shift` to "(1 - _n_)lh" - so `1 = 0lh`, `2 = -1lh`, `3 = -2lh`, and the [newly valid](https://github.com/w3c/csswg-drafts/issues/3698) `0 = 1lh`

2. Get rid of `initial-letters-align`.

3. Change the [block axis positioning algorithm](https://drafts.csswg.org/css-inline-3/#initial-letter-block-position) to something like:

> In the block axis, the initial letter is positioned as if it were a regular inline element on the first, line, respecting its own `alignment-baseline` and the `dominant-baseline` of its parent, and then shifted from this initial position with `baseline-shift` as normal. It does not contribute to the height calculations of the first line.

There's less magic about initial letters than first appears. They're positioned like a regular inline, and content wraps around them like a float.

Finally, to verify I'm not imagining any of this, we've implemented both the currently specified algorithm, and the algorithm described by this issue. There is currently a beta of our layout engine available at https://bfo.com/publisher/?https://bfo.com/publisher/tests/213-initial-letters.xht, with comments inline showing how to try them out.

(Migrated from point three of https://github.com/w3c/csswg-drafts/issues/4171)

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/5015 using your GitHub account

Received on Monday, 27 April 2020 22:26:31 UTC