- From: Florian Rivoal <florian@rivoal.net>
- Date: Tue, 20 Jan 2015 14:13:01 +0100
- To: W3C Style <www-style@w3.org>
Warning: this is a long mail, so go grab some coffee. It should of interest to people who care about the "last line ellipsis" problem, but also about fragmentation (including pagination and multicol), regions, and overflow:fragments / overflow:paged. (If that doesn't get me an audience, I don't know what will :) We have an old resolution to add last line ellipsis as a value to text-overflow (or do something similar), but it doesn't say how it would work. http://lists.w3.org/Archives/Public/www-style/2009Nov/0219.html This isn't the first time this is brought up, because showing an ellipsis at the end of a multi line piece of content is a real problem that needs solving, but doing it as a value of text-overflow is misguided, as that property is fundamentally about inline overflow, and is ill equipped to deal with this use case. If you have 200px of vertical space to display some arbitrary mark up, and want to show an ellipsis if it ends on overflowing, what you have is not an inline overflow issue. - What is the last line anyway? The last line that doesn't overflow at all? The last line that is readable, even if it overflows a little bit? How about if the line doesn't overflow, but its text shadow does? - What do you do for content that isn't inline text? inline-block may be fairly easy to deal with, but what about floats? flexbox? abspos? What we're dealing with is a fragmentation problem, and treating it as such will bring us much closer to the solution that trying to shoe-horn it into text-overflow. Fragmentation is a difficult problem, but one that the group is committed to solving (and has already done to a large extent). In this mail, I am proposing: - extensions/generalizations to some existing properties and pseudos relating to fragmentation to make a more unified model - suggestions on how to build on top of that to support multi-line ellipsis Everything is of course open to bikesheding. == 1 - Generalize 'region-fragment' == 'region-fragment' (http://dev.w3.org/csswg/css-regions/#the-region-fragment-property) currently only does anything on the last region of a region chain. But what it does is much more generally applicable. On a block container element that has too much content to fit, 'region-fragment:break' will cause the element to behave like a fragmentainer. So let's rename ‘region-fragment’ into ‘fragmentation’, move it somewhere else (probably the fragmentation spec) as it does not have depend on much of the regions spec, and give it a definition along these lines: Name: fragmentation Value: auto | break Initial: auto Applies to: block containers Inherited: no On things that are naturally fragmentainers, (pages, columns, non-last regions in a region chain...), 'auto' lets them be fragmentainers as they normally are. On things that are not, 'auto' gives the usual behavior: overflowing content is processed according to the 'overflow' property. 'break' causes the element to become a fragmentainer. Content that follows the break is not rendered (or goes into the next region in the chain if there is one). All the things that can be used in a fragmentation context (break-before, break-after, widows, orphans, box-decoration-break...) can be used. (spoiler: later in this mail, I extend/generalize this property further) == 2 - Generalize 'max-lines' == Let 'max-lines' (http://dev.w3.org/csswg/css-overflow/#max-lines) apply to any fragmentainer. There is no reason to restrict it to any particular kind. == 3 - Adding the ellipsis == This is the least definitive part of this mail. One possible approach would be to do something similar to text-overflow. With the following syntax: fragmentation: auto | break [ ellipsis <string> ]? After layout, remove enough characters and atomic inlines, pushing them further in the fragmentation context if any, from end of the last line box of the normal flow before the fragmentation break, to make room for the ellipsis string to be inserted. If any float or abspos was anchored in the portion that got removed, they get removed as well. Then, insert the ellipsis or <string> adjacent to the line end. If it doesn't fit despite emptying that line box, clip it to what fits. With what we have so far, we solve the common cases of multi-line ellipsis with rules as simple as these: article.preview { fragmentation: break ellipsis; max-lines: 3; } This should work well in the case of textual flows, possibly with atomic inline elements. It also has a predictable, and hopefully reasonable behavior if there are floats or abspos elements anchored in that content. On the other hand, it fails to deal with cases where the fragmentation break occurs in monolithic content or things like flexboxes which don't have line boxes, and unless we can come up with a good solution for that, the ellipsis value should probably be made into a no-op for these cases. Also, while the behavior seems reasonable to me in the case where the fragmentainer is not part of (or is the last of) a fragmentation context, it is not so nice otherwise, as the fragmentation break is no longer at the best point. In the rest of this mail I am assuming this syntax, but other points should be valid even if you disagree with this part. An alternative would be to do something similar to what the regions spec proposes as a processing model for ::after. Having 'fragmentation: break block-ellipsis' on a element which is overflowing (and therefore causing fragmentation) would insert a ::block-ellipsis pseudo element formatted as a block container, whose height reduces the available height in the fragmentainer. This is similar to what Tab proposed here: http://lists.w3.org/Archives/Public/www-style/2012Jul/0688.html This has the advantage of working with any layout system, and is well suited for inserting sentences like "Continued on the next page...". On the other hand, it does not support just adding "..." at the end of the last line. However, the previous suggestion can, and both can be offered as alternatives to authors to pick from. == 4 - 'overflow:fragment' and 'overflow:paged' == Both 'overflow:fragment' / 'overflow:paged' and 'fragmentation:break' cause a regular element to become a fragmentainer. The only difference is what happens to the content left over after fragmenting. This suggest that they should be rolled up in a single property, and 'fragmentation' is just right for this job. Moreover, Figure 5 in the region spec gives a good explanation of how 'overflow: visible' or 'overflow: hidden' interact with 'fragmentation:break' interact, while the overflow spec has issues (Issue 13/14) on this topic with no obvious right answer. These issues go way if we move 'fragment' and 'paged' to be values of 'fragmentation' (renaming 'fragment' to 'clone'): fragmentation: auto | [ break | paged | clone ] [ ellipsis <string> ]? Unifying the models also gives us the benefit that the ellipsis mechanism now works in each of them. == 5 - Explaining regular pagination / fragmentation == We could also try to use the above as a primitive to explain regular pagination by having 'auto' compute to 'paged' when applied to a page box. We could also have an explicit value to turn off fragmentation on things that would normally fragment, and use it as the computed value of 'auto' on ordinary elements. fragmentation: auto | none | [ break | paged | clone ] [ ellipsis <string> ]? If some values don't make sense in some situations, we can always make then compute to one that does. For example, 'clone' could computes to 'paged' on page boxes, while on descendants of elements where 'fragmentation' computes to 'paged', 'paged' could compute to 'none'. == 6 - Pseudos Elements == On an element where 'fragmentation' computes to something other than 'none', ::after (and probably ::before) should have the same processing model as it has on regions (i.e. it is always a block container). An ::ellipsis (for 'text-overflow:ellipsis' and 'fragmentation:break ellipsis') and/or a ::block-ellipsis (for 'fragmentation: break block-ellipsis') pseudo should be introduced, with the appropriate property restrictions, to provide extra styling to the inserted text. == 7 - Pseudo classes == Having made the generalization in point 5, fragment styling (http://dev.w3.org/csswg/css-overflow/#fragment-styling) should probably also be generalized to apply to all types of fragmentations. == 8 - What about multicol == Column boxes are also fragmentainers, and if we explain fragmentation through the 'fragmentation' property, one of the values should explain what is happening to columns. Currently, we have no selector to get at column boxes, but the pseudo classes from the previous point could probably be made to apply here as well (and if not, I expect we will eventually have columns selectors anyway). So we would need to decide if one of the other existing values can be used to explain column fragmentation or if we need a new one. Maybe 'clone' can work here, since we're repeating boxes of the same type. == 9 - Regions == The current regions model works fine under this system. Just define that: - 'auto' computes to 'none' on the last region of a chain, and to 'break' on other regions - overflowing content goes into the next region of the chain if there is one and the if computed value of 'fragmentation' on the overflowing region is 'break' Also, if we're ever interested in introducing something similar to regions, except without naming the flows, we could also have a functional value to the 'fragmentation' property that takes a selector, and puts the overflowing content there. This seems decently expressive: I've picked a few interesting examples from the regions spec, and they can be nicely rephrased in these terms (Filtering examples like example 4 don't do so well). Note that I am not pushing for this as a complement or an alternative to regions, just noting that this 'fragmentation' syntax lends itself well to this sort of extension. ex2: <article> ...some content... </article> <aside> ad or image content </aside> <div class="bottom"></div> article { max-height: 80vh; fragmentation: insert-into(#bottom); } ex3: <nav> ...some links... </nav> <div class="menu"> <nav></nav> ...some more links... </div> nav:not(.menu > *) { fragmentation: insert-into(.menu nav); } ex6: article { column-count: 2; flow-from: article; height: 6em; column-gap: 1em; fragmentation: insert-into(#remainder); } <body> <article>...</article> <div id="remainder"></div> </body> == 10 - What about event handling? == Handling clicking and other events in the ellipsis definitely has valid use cases, but this is a general css problem of event handling in pseudo elements, not something specific to ellipsis. However we solve that will work here. == 11 - What about generated content? == "continued on page 13...", with 13 being dynamically generated, or similar things, preferably also with links, is a reasonable thing to want in ellipsis text. This is also a problem that goes beyond ellipsis, and should use a generic solution. This looks similar enough to the problem solved by 'string-set' and the 'string()' function (http://dev.w3.org/csswg/css-gcpm/#named-strings), so we should reuse that (if we keep it at all). == Conclusion == I think a generalization of 'region-fragment' into 'fragmentation', together with the other related adjustments suggested, would expose a useful primitive, and help unify and simplify how a number of fragmentation-related features work, and open up some new behavior. On top of that, I think this would be a good basis for building multi-line ellipsis, even though I'll readily admit that this part needs more work. If anybody has made it this far, I'd love to hear what you think. - Florian
Received on Tuesday, 20 January 2015 13:13:27 UTC