18

Old school typesetters never allowed the last line of a paragraph to be just nearly filled. Either it would be clearly shorter than the other lines or it would be stretched to align with the right margin. (I assume the standard value where they decided to stretch the line was the depth of the indentation.)

If you have the time the same result can be achieved by checking all pages and manually adding \parfillskip=0pt to all offending paragraphs.

For example, this document:

\documentclass{article}
\usepackage[paperwidth=3in,paperheight=3in]{geometry}
\begin{document}
\thispagestyle{empty}
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
sasdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdfxx 
\end{document}

renders to a paragraph with an almost-full last line:

Almost-full Paragraph

I've earlier been told that it would most probably be impossible to get pdfLaTeX to do this automatically.

If this is true -- could it be done with LuaTeX?

7
  • 5
    I............don't............think...........it's...........a..........good............idea.
    – percusse
    Jan 17, 2012 at 20:51
  • 1
    @percusse Note that I only want to stretch these last lines by 1em (the typical indentation) which shouldn't be more -- I don't have the actual figures handy -- than also the other lines in the paragraph will be stretched.
    – Florian
    Jan 17, 2012 at 21:00
  • 1
    Sorry if that was slightly rude. What I meant is that the same old school typesetters also (AFAIK) start handling the issue by considering the whole paragraph via eyeballing the pleasing cheats here and there. Probably that is the reason why there needs to be some sort of a human hand touching the issue.
    – percusse
    Jan 17, 2012 at 21:04
  • 1
    Perhaps related: Ensure minimal length of last line
    – cmhughes
    Jan 17, 2012 at 21:11
  • 1
    @percusse No offense taken! You are definitely right in the matter: Visual control is needed, but it would anyway be nice not to have too many things to correct manually. And if anybody could come up with a solution, that would take the rest of the paragraph into account, that would clearly be better than only stretching the last line!
    – Florian
    Jan 17, 2012 at 21:21

3 Answers 3

3

This can be done in LuaTeX using the pre_linebreak_filter callback: Before the "normal" linebreaking, do a "trial" linebreak and measure the effective parfill. If it is below the current value of \parindent, set the parfill for this paragraph to zero. Additionally, it can sometimes happen that \parfillskip=0pt fails. In this case this will stretch the last line without affecting the remaining paragraph, leading to a potentially very stretched line. If you prefer to just accept a nearly filled line in this case, just delete the part surrounded by

luatexbase.add_to_callback("post_linebreak_filter", function(head)
  ...
end, "drop_short_parfill")
\documentclass{article}
\usepackage[paperwidth=3in,paperheight=3in]{geometry}
\usepackage{luacode}
\begin{luacode*}
  local glue = node.id'glue'
  luatexbase.add_to_callback("pre_linebreak_filter", function(head)
    local copy = node.copy_list(head)
    local parfill = node.tail(copy)
    if not parfill.id == glue or not parfill.subtype == 15 then
      texio.write_nl'Unable to find the parfill. Disabling special handler.'
      node.flush_list(copy)
      return true
    end
    local lines, infos = tex.linebreak(copy)
    local last = node.tail(lines)
    if tex.parindent > node.effective_glue(parfill, last) then
      node.flush_list(lines)
      copy = node.copy_list(head)
      parfill = node.tail(copy)
      node.setglue(parfill)
      lines, infos = tex.linebreak(copy)
      local t = node.tail(lines)
      if t.width == node.rangedimensions(t, t.head) then
        parfill = node.tail(head)
        node.setglue(parfill)
      end
    end
    node.flush_list(lines)
    return true
  end, "drop_short_parfill")
  luatexbase.add_to_callback("post_linebreak_filter", function(head)
    local t = node.tail(head)
    local tt = node.slide(t.head)
    while tt.id ~= glue or tt.subtype ~= 15 do
      tt = tt.prev
    end
    if tex.parindent > node.effective_glue(tt, t) then
      node.setglue(tt)
      local n = node.hpack(t.head, t.width, "exactly")
      head = node.insert_before(head, t, n)
      n.next, t.head = nil, nil
      node.free(t)
    end
    return head
  end, "drop_short_parfill")
\end{luacode*}
\begin{document}
\thispagestyle{empty}
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
sasdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf

asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
sasdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdfx

asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
sasdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdfxx
\end{document}

In the example, the first paragraph does not need any change. The third automatically gets \parfillskip=0pt and for the second paragraph, only the last line is stretched.

enter image description here

In case you wonder why the result of the "trial" linebreak is always discarded instead of being reused as the actual linebreak, especially if no change is necessary: That would be implemented through linebreak_filter, which is broken in current LuaTeX versions.

5
  • Great! Though the other two answers are very interesting indeed, this was more the kind of solution I was looking for. Just one question: When I run your code on TL 18/ LuaTeX 1.07.0 with the last "x" removed I get a "as-dfx" hyphenation and an extra line. I assume that this would also sometimes occur with real texts. Would it be possible to completely rule out the adding of extra lines, so that I could add this to an existing document without risking new line- and pagebreaks?
    – Florian
    Jun 27, 2019 at 12:26
  • @Florian The question what to do in this case. It happens if TeX decides that there is way with badness below 10000 to align the paragraph, so what should be done then? You could fall back to default linebreaking, but then there are some paragraphs with small parfill again. You could also use an "emergency mode" where always only the last line is stretched to fill the entire line, disregarding the badness. But if TeX decides that this is not an option, it normally has a reason... Jun 27, 2019 at 13:05
  • @Florian I added this to the answer. Jun 27, 2019 at 14:35
  • This is just perfect for me personally. Thanks a lot! For others (eight members have starred this question) who might prefer the reasoning behind your first answer I'd suggest you also leave the other code in your answer. Imo this would make the answer even more comprehensive.
    – Florian
    Jun 28, 2019 at 10:22
  • @Florian I think if you do not want this version, you probably prefer the version without post_linebreak_filter instead of the original one. So in that ease it is easy to modify. Jun 28, 2019 at 10:23
14

Adding a \parfillskip that has a minimum length and infinite stretchability seems like it would do it.

For example, the below sets parfillskip such that the end of the paragraph must have AT LEAST whitespace equal to the minimum of 2 x characters:

\documentclass{article}
\usepackage[paperwidth=3in,paperheight=3in]{geometry}
\begin{document}
\thispagestyle{empty}
%% Measure the width of 'x' in this font
%% and set \parfillskip equal to two times
%% that width.
\setbox0=\hbox{x} \parfillskip=2\wd0 plus1fil 

asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
sasdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdfxx 
\end{document}

Commenting out the \parfillskip=2\wd0 plus1fil line gives:

Without <code>parfillskip</code>

But it shows this when parfillskip is set:

With <code>parfillskip</code>

4
  • 1
    ex is a vertical measure, I think you meant em?
    – morbusg
    Jan 17, 2012 at 22:01
  • Good catch - I updated it so parfillskip is set based on the width of the x character. Jan 17, 2012 at 22:24
  • This indeed solves the problem, but I'd prefer a solution that doesn't add extra lines...
    – Florian
    Jan 17, 2012 at 22:41
  • Was this question ever answered? I am interested whether any automatic method listed above worked in the original aim of automatically flushing right any end of paragraph line followed by at least so much whitespace in the line.
    – user34902
    Aug 11, 2013 at 14:31
14

I'm not sure I'm serious here, but if you are able (and or willing) to retypeset your paragaphs by collecting them first then there is one feature of TeX that would allow you to measure this and act upon it: TeX knows about the length of the last line of a paragraph (or partial paragraph to be precise) if that is followed by a math display. In that case the length of the line above the display is known within the formula as \predisplaysize. Thus you could

  • collect the paragraph material in a macro, say
  • trial typeset it and check out how close its last line is to the full line
  • depending on the result retypeset the paragraph in earnest using a suitable value of \parfillskip, e.g., set it to 0pt or set it to 2em plus 1fil or whatever.

Here is the code to check it out:

\newdimen\mydim
\def\checkit{%
  \mydim\hsize
  \advance\mydim by -\predisplaysize
  \advance\mydim by 2em
  \typeout{There is \the\mydim\space  space available on the last line}%
}


asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdf sadf
asdfjk adsf af dsasdf f dasf fdsa fds afsd fdsaf asdfsdafdsa asdf dsaf asdfxx 
$$\checkit$$

The 2em are substracted because the \predisplaysize is the length of the last line + 2em. For your example that gives us:

There is 4.98819pt space available on the last line

There are some special conditions: if the last line is not typeset at its natural width then the size given as \maxdimen, e.g., if \parfillskipwas set to 0pt, and if there was no previous line then it is set to -\maxdimen but both cases could be controlled in your situation.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .