-
Notifications
You must be signed in to change notification settings - Fork 636
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
[css-text] Preventing too-short final lines of blocks (Last Line Minimum Length) #3473
Comments
There are already controls for widows and orphan lines and page/column breaks in https://drafts.csswg.org/css-break/#widows-orphans. A control for widowed words on the last line could be useful, but it doesn't exist yet. However, I suspect it needs to be paired with a better line breaking algorithm than the current greedy one to achieve good results. If all the lines of the paragraph are re-balanced to push the needed word(s) to the last line, all may be fine, but just pulling from the line before last is probably going to lead to sub-optimal results. Also, we'd need to figure out what this means in languages that do not separate words with spaces, such as Japanese or Chinese. I suppose there's prior art in other software, and we should have a look at what they do there. InDesign maybe? |
We have previously talked about the idea of being able to specify a minimum last line length, but in characters or a percentage of width, not in the number of words: |
@s10wen chiming in on current work arounds. If you're willing to put presentational matters in html, like your demos use of |
Wouldn't a |
Hey all, thanks for the conversation around this. https://drafts.csswg.org/css-break/#widows-orphans seems to be most likely what I'd like to see. Is there anywhere I can see the progress of this being implementing to browsers and test? |
I've just changed the title of this issue to make it clearer that we're talking about orphaned words on a line, not orphaned lines on the top/bottom of a page or column (which is what the I came here because of this thread by the CSS-Tricks team about workarounds to avoid bad-looking breaks. This is clearly a common situation where people are modifying their markup to get typographically pleasant results, and that is really a problem that CSS should try to solve. CSS Text 4 has a heading for this topic ("Last Line Minimum Length"), with an issue summary but no solution, with a cross-reference to a 2015 mailing list discussion. Copying over the current issue text from the spec:
My own opinions to get the discussion started: I think the property should support a minimum number of characters (as an alternative to minimum % of inline size or minimum line length) for the final line. That covers most typographic style guides while still avoiding any discussion about what is or isn't a word across different languages, or when words are broken by hyphenation. Since we're assuming that most implementations will be using a greedy line-breaking algorithm, maybe the property could accept a second value that would be the minimum number of characters for previous lines, at which point no more attempt at removing the widow should be made. (If the second value isn't specified, an auto behavior applies: don't make the second-to-last line shorter than the last line while trying to make the last line longer! In fact, this should apply regardless of whether you also specify a minimum length for the second-to-last line!) If a more complicated
Or maybe it would be better to have two properties: PS, I think it would also be helpful if there were some figures in the spec about widows and orphans to make it clear that those properties aren't about bad line breaking, but about bad block breaking. If anyone wants to create that, I'm sure a PR would be welcome! |
I want to add my two cents to push this conversation further. The following HTML after being "prettified" will break: <blockquote>
Lorem ipsum then prettify will push closing tag to a new line
</blockquote> And in CSS, this quotation mark might appear on a new line, and there is no way around it, except to disable prettifying code and making sure blockquote ends on the same HTML line of code. I wish there was a way to target blockquote:after {
content: '"'
} |
There's a similar looking problem around too-short first lines, if the in-line alignment does not match the reading direction. See the BBC Subtitle Guidelines section on Breaks in justified subtitles for example: |
That only happens with explicit breaks tho, correct? We'll otherwise always fill the first line approximately the same as subsequent lines. |
Further to my comments in the above thread, and following @kojiishi's response, I talked at length with other designers at Clearleft and it was surprisingly difficult to come to a definitive conclusion, particularly around the exceptions. By which I mean: put simply one doesn't want just a single word on the final line of a block, but what's the effect of bringing down a word from the previous line in order to address that? If you were fixing this manually, there might be a ripple affect back up the paragraph until the best overall text shape is achieved. I doubt that's something a browser could afford to do, given the (understandable) reluctance to implement any justification routines beyond the crudest greedy method. The best conclusion we could come up with was something similar to the solution proposed by @AmeliaBR. Set a minimum character length for the final line along with a maximum number of characters to bring down from the previous line. This would be conceptually similar to the hyphenate-limit-chars property.
where It might be useful to some people for the same approach to be expressed as percentages of box width instead:
where It might be that the two methods (chars and %) could be mixed. |
I've put together a (very) rough-and-ready proof of concept here. The idea is to have something to test out the concept of a minimum final line length and maximum amount of text that can be brought down from the line above to address that. Please feel free to have a play, copy, adapt and generally improve. Comments very welcome, here preferably. |
A question on an edge case came up in mind: what to do with a paragraph with "[short word] [long word] [short word]"? An example (there might be better examples but...):
or
The former is better, no? |
Agreed, the former is better. This would be handled by the |
I dup'ed #2396 to this issue. From that other issue, I said:
We are now getting even more requests for this. (Every time this comes up, I always go searching for which property controls this, only to be surprised yet again that there is no way to do this and it's impossible.) It's probably also worth noting that the requests we have for this feature are not about the number of "words" on that last line, as that necessarily doesn't actually solve the visual problem when the last n words are short (or, you're writing in Chinese and the last few words are each just single characters). The request, instead, is to say "the last line is at least x% of the width of the block container." |
In InDesign, because what you see is what you get, adjusting some inconspicuous gaps between characters in the penultimate line should work. Another approach is applying a GREP style, indicating that the last few characters/words in a paragraph cannot be broken into two lines.
If there are not many paragraphs, using See also Handling of Widows and Orphans in clreq and Widow Adjustment of Paragraphs in jlreq. |
The CSS Working Group just discussed
The full IRC log of that discussion<fantasai> myles: this is a small issue ....<fantasai> myles: We have had many requests throughout the years where typographers and designers have come to us and show us a paragraph on the web page <fantasai> myles: they'll point to last line and say, 'this last line is tooooo narrow" <fantasai> myles: this has a name, it's called orphans and widows <fantasai> myles: also term has two meanings <fantasai> myles: CSS has support for the other meaning (pagination) <fantasai> myles: but for this one, doesn't <fantasai> myles: this is one of our highest requested text-related features <fantasai> myles: so it would be cool if CSS could solve <fantasai> myles: problem here is that last line is too narrow, so get wide paragraph and maybe one word on last line. Looks bad <fantasai> myles: more nuance, but what I will say is, I think there's two potential solutions <fantasai> myles: one is a new property, and one is a change to value space of `text-wrap: pretty` <Rossen_> q? <fantasai> myles: so could invent a new property or add a thing that when you do pretty, try to focus on the last line <Rossen_> ack emilio <fremy> lol <florian> q+ <iank_> q+ <fantasai> florian: text-wrap: pretty solves this and more, and is expensive <fantasai> florian: and that's important <fantasai> florian: if it wasn't expensive, just using pretty would be fine <fantasai> florian: there are terrible solutions to this problem <fantasai> florian: if you implement one that is "good enough" <Rossen_> ack florian <fantasai> florian: is there enough perf difference with pretty that it's worth a separate control <fantasai> hober: very significant perf difference <fantasai> myles: naive implementation of pretty is exponentially bad perf <fantasai> myles: whereas an algorithm that just focuses on this problem would be at worst linear, but almost constant time <nicole> q+ <Rossen_> ack iank_ <fantasai> iank_: yes, expensive, but we might have different perspectives on how expensive we're willing to tolerate for pretty <fantasai> iank_: lot of nuance there, let's not get into it now <fantasai> iank_: from our perspective, if there is a control, it would be nice if it could also control pretty <fantasai> iank_: fundamentally, pretty does have a lot of knobs like "how much to bias for x consideration" <fantasai> myles: if independent control for last line and pretty, browser could see and modify pretty to focus on last line <fantasai> iank_: potentially <fantasai> myles: that sounds great <Rossen_> ack nicole <fantasai> nicole: I wanted to ask, how many lines would be impacted by that <fantasai> myles: I think we're flexible here, not super clear what the spec should say <astearns> -1 to only stealing words from one line <fantasai> myles: if we were to implement this, first version would start at 1 line and then iterate from there and see if need to increase <fantasai> nicole: similar to headline balancing? <fantasai> iank_: not really <florian> q+ <fantasai> hober: taking a first pass would only use one line, but I can imagine empiricially discovering that we can tolerate 3-4 and might have a spec, but let's not prematurely decide <Rossen_> ack florian <fantasai> florian: are you aiming for a yes/no property or are you thinking of giving author control like at least 3em or at least 30% <fantasai> myles: flexible <fantasai> myles: Firstly, we now using words is long <fantasai> myles: not clear, in i18n context, what exactly a word is <astearns> s/long/wrong/ <fantasai> myles: from implementation perspective, can make a boolean <fantasai> myles: if authors need more control, can add <fantasai> myles: when authors request this, they usually request a percentage <fantasai> myles: e.g. at least 15% <florian> q? <Rossen_> s/15%/50%/ <fantasai> iank_: is that what they actually want, or think that's the tool that indesign provides... <fantasai> myles: I don't know, but my proposal is a boolean switch <florian> q+ <astearns> q+ <fantasai> myles: and as implementations progress, we can see if that makes sense or not <florian> q- later <fantasai> smfr: should we just resolve on adding a property without specifying if boolean or not? <Rossen_> ack astearns <fantasai> astearns: for a prperty that does only this one thing <fantasai> astearns: I would advocate strongly for just a boolean switch <fantasai> astearns: anything more finely grained is really going to have to be weighed against other line-breaking considerations <fantasai> astearns: and needs to modify results of pretty <fantasai> astearns: if we're separating the two, then the simple thing, should just be a boolean <fremy> q+ <Rossen_> ack florian <fantasai> florian: I think I support this because of the perf difference <fantasai> florian: but even then it does feel like it's a variant of pretty, you've decided what you care about <fantasai> florian: so is it really separate from pretty? <fremy> wanted to say the same thing <fremy> q- <emilio> fantasai: There are a lot of knobs that factor into pretty and a lot of them are already separate knobs <fremy> q+ <emilio> ... In level 4 or some other we had word-spacing and letter-spacing give you the optimal value but we also give you a range for the line-breaker to play with <iank_> q+ <emilio> ... that's a factor into the line breaker <emilio> ... same for hyphenation controls <emilio> ... these are already split out into multiple controls <emilio> ... turning on pretty shouldn't need to redeclare your controls <emilio> ... so should cascade separately <emilio> ... I agree with myles, we should have this tweak <florian> [Florian is convinced] <emilio> ... I'd like something that can be applied to the html spec and not spin for 10 minutes <emilio> ... I also agree with astearns that you have to look a more than one line <nicole> q+ <emilio> ... A boolean switch is fine but we should define this thinking of extending it to percentages too <myles> sooooo `minimum-last-line-length: normal | auto [maybe more in the future]` <emilio> fantasai: If we spec it out we need to pick a name that's gonna work for both <emilio> ack fantasai <florian> q+ <fantasai> fremy: I have a similar thought, that just looking at one line doesn't cut it <Rossen_> ack fremy <fantasai> fremy: you will end up with a triangle. If you have only two lines it's fine, if you have three, you'll have a long first line, then a smaller second line, and even smaller third line. Very strange <fantasai> fremy: so in a way I'm struggling, if you have elements with more than two line, oh an implementation can produce good results without balancing lines from where it's stealing the words <fantasai> fremy: I don't think I can see how to make it different from pretty <fantasai> fremy: if you're doing that you're back with pretty <fantasai> astearns: I am convinced that there is a faster linebreaking algorithm that would only do this and not look at the full "pretty" penalty values <fantasai> astearns: it doesn't have to be as expensive as the full pretty implementation to do just this one thing <fantasai> iank_: but you can bias the pretty implementation <emilio> `text-balance: pretty-fast` :-) <Rossen_> ack iank_ <fantasai> iank_: One thing on controls, it's not clear how this control would apply to `text-wrap: balance` <fantasai> fantasai: it wouldn't <astearns> but I absolutely agree that we should avoid introducing the line wrap triangle shapes that fremy described <fantasai> myles: `balance` will win. it handles last line by itself <fantasai> iank_: it feels to me that we have at least 3 line-wrapping algorithms, might have others in the future <fantasai> iank_: this control wouldn't affect balance <fantasai> fantasai: it also doesn't affect nowrap <fantasai> iank_: sure <hober> qq+ <fantasai> iank_: I somewhat agree with fremy that you might be getting into pretty territory <ntim> q+ <fantasai> iank_: I'm not convinced by the global control argument <fantasai> hober: if setting balance means it doesn't apply, then you aren't setitng whatever this is <ntim> +1 to what hober said <fantasai> hober: you're not setting it if you're not setting `text-wrap: balance` <Rossen_> ack hober <Zakim> hober, you wanted to react to iank_ <Rossen_> ack nicole <fantasai> nicole: does anyone want orphans? is anyone like "I want to turn off nice ending to my text" <fantasai> astearns: yes, because you will get faster text composition <Rossen_> ack florian <fantasai> florian: I suspect we all agree, but will say explicitly, when that is turned on it is a request for the browser to *attempt* to make the last line not terrible <fantasai> florian: but if can only do this by making some other line terrible, shouldn't do it <fantasai> [agreed] <fantasai> florian: also agreeing with Tess that it's also a text wrap value <fantasai> florian: and can have it as an additional keyword <fantasai> myles: requested resolution isn't any particular grammar <fantasai> ntim: I want to echo what tess said, it feels like text-wrap extension <SebastianZ> q+ <Rossen_> ack ntim <Rossen_> ack fantasai <astearns> fantasai: so all of this is why I did not want this in text-wrap <ntim> q+ <astearns> fantasai: whether and how you are wrapping should be separate <emilio> fantasai: because it needs to cascade separately <florian> +1 <emilio> ... you want to set the controls in a single place in your stylesheet <myles> q+ <astearns> and if we want this to be extensible as a text-wrap: pretty control it needs to be separate <emilio> ... this needs to be a separate thing and honestly I think `pretty` should have as well <emilio> ... I think this gets into how we're wrapping and that should only set once <florian> q? <ntim> q- <fantasai> This is why I was against "text-wrap: pretty" as a syntax in the first place <emilio> Rossen_: let's pause for a second. There's a clear proposal for a clear problem <emilio> ... There are also ideas about how to do it performantly... <emilio> ... we have plenty of engineers on the room, and we're getting into how to solve the issue, let's not do that <emilio> ... let's go through the queue if you want to discuss syntax or how to solve it <emilio> fantasai: what's the proposed solution? <emilio> Rossen_: to have a property or a value that solves this problem in a more performant way to pretty <Rossen_> ack SebastianZ <emilio> SebastianZ: iterating on nicole's question <emilio> ... if the algo can be made pretty fast, why can't it be the default? <emilio> ... we also have text-decoration skip-ink <emilio> ... but it was worthwhile having as a standard <florian> [I think think we should open a separate issue to move "balance | stable | pretty" out of text-wrap, and probably add "avoid-orphans" there] <emilio> Rossen_: let's close that bridge when we get to it <Rossen_> ack myles <emilio> PROPOSED: Add a control that is either a property or a value that causes UAs to make the last line longer than it would've originally done unless that was a bad idea <emilio> RESOLVED: Add a control that is either a property or a value that causes UAs to make the last line longer than it would've originally done unless that was a bad idea |
Just an observations on the question of how to specify the length of the last line (and possibly also the gap on the previous line). It seems to me that using line length percentages, based on the rendered text, is better than counting characters. Counting characters is problematic in a large number of non-Latin languages because they use (often multiple) combining marks, which are combined into the same 2-dimensional space as a base character. For example, 10 characters in some languages can be very short, compared to 10 characters in English, eg. أَنْتُنَّ contains 9 characters, but is only about 3-4 Latin characters in width. Similarly, an emoji such as 👨👩👧👦 contains 7 characters in about the width of a couple of english letters. |
Agreed. See prior comments: #3473 (comment) |
Yep.
|
Hi folks. Writing with my TAG 🎩 on. Given that Chromium is very keen to ship Switching to my CSS WG 🎩 now to discuss specific syntax: What about breaking |
I am unconvinced about decomposing The interaction between these knobs is about as interesting as the knobs themselves: if pretty implies "avoid orphans" and "avoid rivers" and "avoid several hyphenated lines in a row", do we then need not just these three, but also a choice between "avoid orphans unless it would create rivers" vs "avoid orphans even if it creates rivers"? "how about avoid orphans even if it creates rivers or consecutive lines with hyphenations, as long as it doesn't create both, but either way, don't be more than 250% percent slower than brute force line breaking"? A good algorithm for Yes, there's some measure of subjectivity in those tradeoffs, so providing author control is tempting, but:
|
especially as there is already - in theory - control for limiting consecutive hyphens with |
Like For the sake of the argument, let's call that Both
|
If I may leave a note as an author: we noticed Unfortunately, If we could opt into the orphaned-words bit, we would, and it's not obvious to me why bumping a word to the next line would have to change all the hyphenations & multiple lines as its tradeoff, but The opinionated package deal is also worrisome because given how aggressive the treatment of hyphenation is, and how this is not apparently part of any standard, why expect other browsers to adopt this opinion? We definitely wouldn't want to adopt it for Chrome, and then 5 years later discover (or worse, not discover) that it looks terrible on Firefox or Safari after they ship their particular flavor... (Looking into this was not assisted by the very confusing terminology. For example the official Chrome blog which is some of the only documentation on what the shipped * almost all of the discussion & design doc seems to assume left-justification only, and ignore center, right, & fully-justified text, so perhaps this was just an oversight in the tuning? |
Thanks for all the examples, @gwern. I’m not sure I agree they are all failures of the current implementation. Most of the after results appear better or at least as good as the befores to me. For instance, 49/50 does have wider spacing but I think it’s arguably better (spacing is still consistent across the paragraph and a 2-hyphen ladder is removed). And 53/54 does have wider spacing but it might be a case where justification in general is causing problems (there is an unaffected line starting with “reader mode” that looks bad both before and after). The ones I do see a problem with are 65/66 is a bad result, I agree |
I didn't say the current Chrome implementation of But if a lot of the instances aren't clearly better even by your assessment, you can see why we wouldn't be too eager to go out of our way to add & debug fairly exotic new CSS to opt into this brand new, possibly buggy, non-standardized-cross-browser, opinionated package of changes, when there's just one part we are sure we want. And so I am simply mentioning, in the context of the discussion of whether to add knobs, that we would like a knob for that part, and would not like to have use |
One further thing I would mention after looking at the mobile screenshot pairs too, which look better than I expected: https://share.obormot.net/temp/text-wrap_screenshots_mobile.zip It's hard to predict what Chrome I've stared at many of these, but still can't look at the 'before' and predict what will happen in the 'after', particularly how the word-orphans are treated. As best as I can tell, word-orphan fixes are strictly subordinate to the hyphenation changes: it doesn't seem like the word-orphans ever get changed unless there is a previous hyphenation change already being made, regardless of how easy & straightforward a word-orphan change might be. It seems like This is confusing, and I don't think users understand it - I don't recall any of the people advertising |
CodePen example:
https://codepen.io/s10wen/pen/GPWWyP?editors=1100#0
Tweet + replies:
https://twitter.com/s10wen/status/1076079575506083840
Wikipedia explanation:
https://en.wikipedia.org/wiki/Widows_and_orphans
CSS Text 3 w3 Spec:
https://www.w3.org/TR/css-text-3/
The above links have led me here, to further pursue this. I'm wondering if anything currently exists, or could be implemented to handle this. My idea is that
orphan: 2
would always leave two strings of text together, please see the CodePen for an example. Or, it could be thatorphan: true
, would mean that orphans always had at least 2 words.