[csswg-drafts] [css-inline-3] initial-letters; feedback from implementation (#4171)

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

== [css-inline-3] initial-letters; feedback from implementation ==
We've just been working on initial-letters and have some feedback. As far as I'm aware it's only webkit (with a prefix) offering a partial implementation on this (is this true?). So I realise this specification is in its early days. Here's what we've come up with:


### 1. Allow shape-margin
For `initial-letters-wrap: all`, there's a definite need for the `shape-margin` property, which should apply either to the shape manually  specified with `shape-outside` or to shapes derived from the glyph outlines. Relying on the end-inline margin is not not flexible enough, say for letters like "Γ" (greek capital Gamma) where you want to push text away from the protruding horizontal stroke, or "C", where without a margin the text could disappear into the mouth of the "C"

### 2. Auto-sizing of element and effect on linebox
Inline initial-letters are auto-sized around the visible bounds of the content; around the size of the glyphs. What's not clear if whether this sizing algorithm should affect the linebox or not:
![e](https://user-images.githubusercontent.com/989243/62414920-de358d80-b619-11e9-87cb-e0309ee984ff.png)
left-most: our rendering, where we auto-size the initial-letters elements as specified, but we don't push the linebox up to accomodate it.
middle: webkit's rendering, where the box extends down as far as required, but never goes above the cap-height of the letter.
right: a third option - the element is sized to fit the content, and the linebox is pushed up to include it.

You could make coherent arguments for the left or right renderings fairly easily; probably the left is more useful, as otherwise the position of the first line relative to the paragraph will depend on its initial letter, which is likely to annoy someone at some point. I'm not sure Webkits approach will work for non-latin scripts; It's not testable as Webkit doesn't seem to support anything other than alphabetic alignment. Which brings me neatly to my next point.

### 3. Alignment and font-sizing

Alignment and sizing are two sides of the same issue for initial-letters. The specification goes to some length to describe how the letters have two alignment points - a two-line drop letter "A" would have its top top alignment at the cap-height of line one, and it's bottom alignment at the baseline of line two, as shown with this example from the spec:
![](https://drafts.csswg.org/css-inline/images/Dropcap-lines.png)

But this description only makes sense for fully dropped letters, i.e. where `sink==size`, and it necessarily fails if the lines aren't all the same size as the block element's `line-height`, as demonstrated in https://drafts.csswg.org/css-inline/#initial-letter-block-position.

So while this description is helpful to understand the _intention_ of the specification, I think it should be clear it's no more than that. When you come to implement it, you're positioning the text on the first line without any knowledge of what's to come. Layout necessarily goes like this:

1. Create the text with a font-size derived from the algorithm given in https://drafts.csswg.org/css-inline/#sizing-initial-letters (this may vary depending on the chosen alignment baseline; I haven't worked this through yet)
2. Align the initial-letters subtree on the first line, using the normal inline alignment rules - i.e. for alphabetic alignment, align the alphabetic baseline of the initial-letter with the alphabetic baseline of the linebox.
3. Shift the letter up or down by an appropriate amount based on the "sink" value from `initial-letters`.

For alphabetic text this is effectively what the spec says already. But when aligning on a hanging baseline (for example), the outsize letter will effectively drop without any further shift.

![hindi](https://user-images.githubusercontent.com/989243/62417458-b3fac480-b647-11e9-8352-0d37a476d491.png)

The Hindi initial-letter was aligned on the hanging baseline of the first line. That's all. No further shift was required. In fact with `dominant-baseline: hanging` on the paragraph and `alignment-baseline: baseline` on the first-letter, there's no need for the `initial-letters-align` property to exist at all.

I would suggest dropping it completely and simply using `alignment-baseline` exactly as it is defined for regular inline elements, although perhaps disallowing `top`, `middle` and `bottom` as values.

"But what about atomic inlines", you may ask?

### 4. Atomic inlines

Atomic inlines such as images can be used as an initial letter as the spec is now, and the intention appears to be that they're aligned based on the same alignment points derived from the `initial-letters` property. But the spec is silent on the mechanism for this. I presume the height is to be set then the width scaled to match? For example, if using an image:
```
<p>
 <img style="initial-letters: 3" src="fancy-I.png"/>nitial letters can be images too
</p>
```
then the image will need to be scaled to cover three lines, and its top aligned with the top of the first linebox. Again there's no need for `initial-letters-align` - in fact the result should be identical to the layout results you'd expect from this:
```
<p>
 <img style="alignment-baseline:top; height:3lh" src="fancy-I.png"/>nitial letters can be images too
</p>
```
or, for that matter (and discounting the differences in break opportunities):
```
<p>
 <img style="float: start; height:3lh" src="fancy-I.png"/>nitial letters can be images too
</p>
```
The same process applies to elements with `display:inline-block` and other inline content generating it's own formatting context (see also https://github.com/w3c/csswg-drafts/issues/4116)

### 5. More flexibility in initial-letters values

The initial-letters element itself takes two numbers, the first ("size") determining the font-size via quite a complex algorithm, the second ("sink") the number of lines to shift up and down - not directly, as shown in the hindi example above; it's more a statement of intention as to where the bottom of the drop-cap should be.

As it is now, the "sink" value must be a positive integer and defaults to the "size" value. I don't see why this limitation is required. Make it a number or length describing the vertical shift of the initial-letter. If specified as a number, treat it as if it had an implied "lh" unit. If undefined, the value depends on the baseline: for alphabetic, it's the same as "size", for hanging or hebrew it's 0. And allow it to be less than zero if that's what the user wants.

Why? First, (like @tabatkins I believe) I dislike unitless numbers; make them a length so you can do interesting things with calc(). Second, by making this number explicitly "the distance to shift the text" it removes any confusion around the fact that `hanging` baseline text is dropped by default, whereas `alphabetic` baseline text is not. It also opens up other alignment options, such as `center` or `mathematical` which might need finer adjusting (I can't think of a use for these, but I can't think of a reason to disallow it either). Finally, it's no harder to implement.

Thought of as a length, it's functionality is identical to `baseline-shift`. I'll leave that out there, perhaps greater minds than mine will have some ideas on reconciling that.

### 6. Non-replaced inlines as part of the initial-letter

The idea is you can have an initial-letter made up of more than just a single text node. It is described in the specification and even an [example](https://drafts.csswg.org/css-inline/#example-3b236413) given, with image:
![](https://drafts.csswg.org/css-inline/images/firstmost-inline.png)

This corresponds to
```
<em style="initial-letters:2"><b>This</b> phrase</em> ...
```
While I think this isn't a bad idea, the problem will come with the font size. The spec states that initial-letter sets the _used_ font-size only, not the computed size. Well, this becomes a problem when you have more than one element. The nested `<b>` above would inherit the _computed_ font size, not the used size: effectively it's getting the font-size of the parent block.

You could say that font-size is calculated with the magic algorithm for the entire subtree, but that seems unnecessarily restrictive. I can't think of many situations where this would be an actual problem; there may be better examples in other languages, but in English the best I can do is:
```
<p>
<span style="initial-letters:3">1<sup>st</sup></span> November
has an initial letter with two font sizes, and a baseline-shift based on the font-size.
</p>
```

Frankly, for all the hoops you would need to jump through to fix this, you may as well just say that using the `initial-letters` property on an element sets the _computed_ font size, not just the used one. I'm not sure what problem the current specification was trying to avoid by doing this, but I'm not sure it's worth it.

### 7. Width or height set explicitly on the initial-letters element.

The spec [explicitly allows](https://drafts.csswg.org/css-inline/#initial-letter-box-size) to have width or height specified, and section 5.7.1 describes how to align content within this box if required, using `text-align` or `align-content`.

Personally I think this is a very bad idea. Inline, non-replaced elements do not take a width or height anywhere else in CSS, and I think there's no need to make them do so here. If you want to give the box a particular size, just use `display` to set to `inline-block` or `inline-flex`. If you're setting `initial-letters` on a <span> this trivially allowed; allowing limited values of display to be set on a ::first-letter pseudo-element is perhaps not an insurmountable issue either. I think this approach would be easier to understand and implement than describing some special, initial-letters only alignment rules for elements with `display: inline`.


### Summary

Lots there, but to sum up my suggestions would be:

* allow `shape-margin` on initial-letters
* decide whether the inline-element size has any impact on the linebox size, and state the decision explicitly.
* don't disallow `alignment-baseline` and `baseline-shift` on initial-letters; use them as you would normally for inline content, and instead drop the `initial-letters-align` property.
*  make the "sink" component of `initial-letters` _the vertical distance to shift the letter_ (a length), rather than _the number of lines the initial letter should sink_ (a unitless value). Set its default based on the initial-letters "size" *and* the alignment. 
* don't allow `width` and `height` on initial-letters with `display:inline`. If these are required, the user can use `inline-flex` or `inline-block`.
* Make the magical value of font-size a computed value, inherited as normal and impacting on "em" units, rather than just a used value.

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

Received on Sunday, 4 August 2019 00:49:32 UTC