Skip to content

[css-inline-3] Initial-letters layout can be improved #5015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
faceless2 opened this issue Apr 27, 2020 · 17 comments
Closed

[css-inline-3] Initial-letters layout can be improved #5015

faceless2 opened this issue Apr 27, 2020 · 17 comments
Labels
Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. css-inline-3 Current Work

Comments

@faceless2
Copy link

faceless2 commented Apr 27, 2020

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

image

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

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

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 #4171 (comment); 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-aligncompletely. 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

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 0 = 1lh

  2. Get rid of initial-letters-align.

  3. Change the block axis positioning algorithm 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 proposed in 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 #4171)

@zed-vector
Copy link

@faceless2, (At a first reading) I do not fully follow your propsal, it is all a bit technical for me, and I am probably therefore being dense, but it seems to me you are considering only the dropped initial case. The raised initial and (partially) sunken initial cases are not meant to be top aligned,
https://www.w3.org/TR/css-inline-3/#propdef-initial-letters

@faceless2
Copy link
Author

Not at all - the dropped initial letters make it easier to illustrate the alignment points, that's all.

If you want a raise initial letter, it's no different. For example, lets assume you're dealing with english text and you want it to look like this:
image

With the current spec you would do:

p::first-letter {
    initial-letters: 3 1;
}

This would align the alphabetic baseline of the first letter, with the alphabetic baseline of the first line. That's exactly how you align regular text, of course - there's no "shift" involved, it's just a big letter at the start of the first line. So how would that look?

p::first-letter {
    initial-letters: 3;
}

No need to specify any shift. The letter is where it would be with normal alignment.

I don't think I'm proposing anything radical. We already have the baseline-shift property to move inline content up and down, and the alignment-baseline property to align it - they're normally combined into the vertical-align shorthand. All I'm trying to demonstrate is that:

  1. they can be used for initial-letters with no loss of functionality
  2. doing so makes the process more consistent with the rest of css-inline, and loses some of the limitations we currently have with the layout model.

@zed-vector
Copy link

And if I want this?:
p::first-letter { initial-letters: 3, 2; }

@zed-vector
Copy link

And if your
initial-letters: 3;
does the same as
initial-letters: 3, 1;
how do we get the effect of
initial-letters: 3, 3;
?

@faceless2
Copy link
Author

faceless2 commented Apr 28, 2020

currently specified using baseline-shift
initial-letters: 3 0 initial-letters: 3; baseline-shift: 1lh
initial-letters: 3 1 initial-letters: 3; baseline-shift: 0
initial-letters: 3 2 initial-letters: 3; baseline-shift: -1lh
initial-letters: 3 3 initial-letters: 3; baseline-shift: -2lh
initial-letters: 3 4 initial-letters: 3; baseline-shift: -3lh

The baseline shift is always the line-height times 1 - v, where v is the second parameter to initial-letters (I'm assuming an alphabetic baseline here, to keep it simple).

@zed-vector
Copy link

OK, but how does that make it easier for me to write my webpage?

@faceless2
Copy link
Author

faceless2 commented Apr 28, 2020

Well, that's a fair question I suppose.

Before you can write a webpage using initial-letters you need browser support. Webkit has partial support, behind a prefix. It does lots well, but doesn't handle initial-letter-wrap: all, it doesn't let you set initial-letters on anything other than a ::first-letter pseudo-element (which makes some aspects of this untestable), and the bounds of the first-letter aren't quite right.

My company has now built a full implementation of the spec as it is, although we're building for print, not websites. In my opinion, this part of css-inline is unnecessarily complex. I don't think it works properly for hanging baselines or top-aligned images. Alignment for replaced content is still a bit unanswered. The reference box for shape-outside, the whitespace trimming behaviour implied for initial-letters-wrap: first are not described at all, so we've had to guess (issues pending). And all of this is entirely as you'd expect of any specification that has had limited interest from implementers. It isn't a criticism, it's normal. The spec even says "draft" at the top.

So, with respect, I didn't open this issue to help you write your webpage. I'm trying to test the spec to see how well it works - to improve it[1]. Implementers hate incomplete specs; tightly defining the behaviour means we can write testcases (we love testcases); and no-one wants to waste time implementing something that doesn't fulfil the user requirements. Once it's shown to be easy to implement and cover all needs, it will be added to browsers. And that will help you write your webpage.

I fully appreciate that baseline-shift: -2lh is not as nice as initial-letters: 3 3, not least because "lh" units are still largely unsupported. But redefining the block alignment algorithm to one we already use for inline alignment makes this less of a special case, which is good for everyone. If I can make the argument for that successfully, we can talk syntax.

[1] Also, I also left the last F2F having promised @fantasai I'd try to clean up the previous issue and make a clearer demonstration that regular alignment would work. Results TBD...

@zed-vector
Copy link

Understood.

I am arguing from a user's point of view to try to ensure the eventual recommendation does end up fulfilling user requirements.

I am all for tightening up on handwaving in the spec, and for specifying in a way that is easier to implement (without compromising ease of use, which is paramount). I have a style sheet with a FIXME from 2015 awaiting initial-letters.

@fantasai
Copy link
Collaborator

@faceless2 Thanks for the detailed writeup. :) I think you bring up some good observations here.

I also think @zed-vector’s point, “how does that make it easier for me to write my webpage?” is important. The syntax of initial-letters and the way it triggers various automatic calculations, and the way that initial-letters-align is defined and the fact that it is inherited are decisions that were made to make it easier to write a web page.

You point out that there aren't actually two alignment points, there's one, and a shift. That's a good observation, and I think you're right we can simplify the spec by describing things that way. But both alignment points are necessary because that's how we calculate the correct size for the initial letter. The author can't calculate that size, because they don't have access to the font metrics.

You also point out the case of hanging baselines doesn't work quite as expected for non-integer size values less than the drop value. But under your model, raised caps (size values larger than the drop value) would not really work as expected either. And central baseline alignment also doesn't work as expected.

One of our goals in designing this feature is to let the author declare at a high level what they want to happen: how many lines to drop, how big, with respect to the rest of the text, they want the initial letter to be. And make the UA do all the necessary calculations.

As you point out, some of these calculations could use some fine-tuning. We could have the initial value of initial-letters-align key off of dominant-baseline and alignment-baseline to the extent that they provide extra information. We could adjust the way alignment is done to better handle baselines that aren't on the line-over side of the text.

But fundamentally, I don't think the API is wrong: I think it is communicating between the UA and the author at exactly the right level. We just have to do better at handling the details, particularly for non-Western scripts.

@zed-vector
Copy link

As #4171 is closed, I need to come back to the main point I was making there, and have been trying to get over for quite some time now, that the API is wrong in this respect: where the height of the initial letter is less than the height of the number of lines indented to make space for it, then the initial letter should be aligned at the top, not the bottom.

@zed-vector
Copy link

@fantasai, may I suggest that 'key off of' is perhaps neither the clearest nor most elegant English phraseology, and I think I have recently seen you use it in a draft spec edit. I am not sure exactly what you mean to say, (and venture to suggest that maybe you aren't either) otherwise I could be more helpful with an alternative wording. Perhaps 'be dependent on' or 'be determined by'? But neither of those is really explicit either.

@faceless2
Copy link
Author

faceless2 commented May 28, 2020

Thanks for sticking through to the end! Your comments indicate I should have been clearer on a few things, so I'll touch on those and then stop banging on about this for a bit and let it percolate through.

  1. Despite appearances I'm not necessarily suggesting any changes to the syntax. initial-letters: 3 3 seems quite reasonable, and both yourself and @dauwhe have clearly considered how an author would convey their intentions. My intent is to improve the mechanism of initial-letters alignment, not the syntax. If necessary it could be made a shortcut property to set both baseline-shift and a property to control the font-size, eg. initial-letters-size.

  2. I agree that the "two alignment points" concept is an effective way to convey how the font-size is derived. But once the size is derived I'm not sure that concept needs to hang around for alignment. Subjectively, I find it easier to conceive of the letter aligning with the first line and shifting down, than aligning with where the the third line is probably going to be and possibly shifting up.

  3. Regardless of now the mechanism is defined, I think an important first step is to loosen the definition of initial-letters so the second argument can be a number, not an integer. That will immediately fix some of the limitations shown above. @zed-vector has just commented while I was editing this, so I think it's necessary.

  4. "But under your model, raised caps (size values larger than the drop value) would not really work as expected either.". I'm not really sure what "expected" is here, so that's a difficult point for me to debate :-) Happy to clarify if required. But again, I'm not necessarily making an argument for a change to the syntax.

  5. "And central baseline alignment also doesn't work as expected." - this is for vertical text? Yes, I'm pretty sure I agree - we haven't implemented vertical layout yet so I haven't been able to work up some examples. Again, with initial-letters used as a shortcut property to set baseline-shift, if changes to the algorithm need to be made for vertical layout it could be done here.

Thank you again for taking the time to go through this. I think what's defined now does works for alphabetic baselines, but it could be clearer. Presuming the second argument to initial-letters is changed from an integer to a number then the two approaches lead to identical results. If I've demonstrated that you can describe the process another way and that proves useful, I've achieved what I hoped to.

I don't think what's there now works for hanging baselines, and if I've convinced you of that then I look forward to being part of the discussion on how to improve it. We have a decent testbed now with both algorithms implemented, so can easily trial any ideas.

@fantasai
Copy link
Collaborator

@faceless2 Wrt

But under your model, raised caps (size values larger than the drop value) would not really work as expected either.
I'm not really sure what "expected" is here, so that's a difficult point for me to debate :-) Happy to clarify if required.

In your model, if I'm understanding correctly, initial-letter: raise 3 would not result in a raised initial given a hanging baseline. It would result in a 3-line dropped initial. And initial-letter: drop 3 would result in a initial letter whose top is sunk 3 lines below the first line. That seems kinda weird. The behavior across writing systems should be somewhat consistent.

@fantasai
Copy link
Collaborator

fantasai commented Jul 20, 2020

One possibility for fixing the hanging baseline case, in line with the request in #5329 would be to use the top alignment point when size < sink (in all cases). But not when size > sink, which would give consistent results across writing systems. Thoughts?

@faceless2
Copy link
Author

Currently "raise" is defined as setting an initial letter sink of "1", so yes - with no changes to that definition, that's exactly what would happen, and yes it would be weird. But if the model was going to change the definition of how "raise" was defined would necessarily need to change as well, and I figured we'd cross that bridge if we got there.

Your "alignment-point switch" idea is quite clever. It seems to cover zed-vectors cases for latin, and mine for Hindi, at least with the fonts I tested with. I'll try and get it implemented over the next few days too see how well it works.

I don't know if there are any cases where bottom-alignment would be wanted for latin where size < sink (i.e. the existing behaviour), but short of providing the author a switch to choose (i.e. with align-self) it seems like a much better default behaviour than we have now.

@fantasai
Copy link
Collaborator

@faceless2 Fixes committed for #5329 Is there anything else in this issue you think we need to address, or should we close it?

@faceless2
Copy link
Author

There were several aspects to this, most of them a subjective "things could be a bit simpler" and one technical issue which is certainly fixed in #5329 - we've discussed the other parts, you've considered them and I think that's good enough for me. Thanks, and closed.

@fantasai fantasai added Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. and removed Commenter Response Pending labels Aug 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. css-inline-3 Current Work
Projects
None yet
Development

No branches or pull requests

3 participants