2

I would like to format a paragraph of text such that the width of the paragraph shrinks until the last line in the paragraph is about the same width as all the others.

Here is an example of what I mean in CSS/javascript.

In LaTeX, consider this document:

\documentclass{article}

\begin{document}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam justo sem, 
malesuada ut ultricies ac.

\end{document}

For me, this produces two lines with the break after "sem,", such that the second line is significantly shorter than the first:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam justo sem, 
malesuada ut ultricies ac.

What I want instead is something like this (although the exact position of the line break would depend on the font):

Lorem ipsum dolor sit amet, consectetur adipiscing 
elit. Nam justo sem, malesuada ut ultricies ac.

Any direction would be appreciated, because I've been unable to make any headway searching on my own. I know I could use \parbox or \minipage to make paragraphs of a fixed width, but this needs to be a dynamic width.

3

2 Answers 2

4

This version should work, although it may on occasion generate an overfull \hbox error.

\documentclass{article}

\newcommand{\squeeze}[1]% #1 = paragraph
{\bgroup
  \sbox0{#1}% get total width
  \setbox1=\vbox{\strut #1\strut}% get paragraph height
  \dimen0=\dimexpr \ht1 + \dp1\relax
  \count1=\numexpr \dimen0 / \baselineskip\relax% number of lines in paragraph
  \dimen0=\dimexpr \wd0 / \count1\relax% divide total width by number of lines
  \parbox[b]{\dimen0}{\parfillskip=0pt
    \strut #1\strut}%
\egroup}

\begin{document}

\squeeze{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam justo sem, 
malesuada ut ultricies ac.}

\end{document}

demo


This version increments the width in a loop until the text will fit even with a normal \parfillskip.

\documentclass{article}

\newcommand{\squeeze}[1]% #1 = paragraph
{\bgroup
  \sbox0{#1}% get total width
  \setbox1=\vbox{\strut #1\strut}% get paragraph height
  \dimen0=\dimexpr \ht1 + \dp1\relax
  \count1=\numexpr \dimen0 / \baselineskip\relax% number of lines in paragraph
  \dimen0=\dimexpr \wd0 / \count1\relax% divide total width by number of lines
  \loop
    \sbox0{\parbox[b]{\dimen0}{\strut #1\strut}}%
    \ifdim\ht0>\ht1
    \advance\dimen0 by 1pt
    %loop\par% display number of steps
  \repeat
  \parbox[b]{\dimen0}{\parfillskip=0pt
    \strut #1\strut}%
\egroup}

\begin{document}

\squeeze{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam justo sem, 
malesuada ut ultricies ac.}

\end{document}

In this case the resulting rectangle is only 2pt wider than before.

demo2

1

I tweaked the algorithm posted by John to address some corner-cases I encountered in my document (which had several hundred of these "squeezed" paragraphs).

John's algorithms both begin by calculating the average width of the lines in the paragraph. The algorithm without a loop stops there, and just makes the paragraph that average width. I find that this simple heuristic produces incorrect results fairly often. The algorithm with the loop then increases the width of the paragraph until the paragraph has no more lines than it began with. This is more robust, but also tends to produce paragraphs that noticeably are wider than the width of the page.

My algorithm starts with the paragraph at its natural width, then makes it narrower and narrower until it would be forced onto a new line. This approach worked well for all of my use-cases. I also centered the text and disabled hyphenation, since that was the style I wanted, but this could be easily changed.

\documentclass{article}

\usepackage{etoolbox}
\usepackage{hyphenat}

\newcommand{\squeeze}[1]%
{\bgroup

  % Make a natural-size box for the paragraph.  Note the initial width and 
  % height of the paragraph.
  \setbox0=\vbox{\centering \nohyphens{#1}}
  \dimen0=\wd0
  \dimen1=\dimexpr \ht0 + \dp0

  % Keep making the box narrower and narrower until it the text spills onto 
  % another line.  Use a generous threshold (\baselineskip / 2) for deciding 
  % when another line has been used, because the box can change height slightly 
  % as words move around.
  \dimen2=\dimexpr \dimen1 + \baselineskip / 2\relax
  \whileboolexpr{
    test {\ifdimless{\dimen1}{\dimen2}}
    and
    % Without this test, trying to squeeze a single word would cause an 
    % infinite loop.
    test {\ifdimgreater{\dimen0}{0pt}}
  }{
    \advance\dimen0 by -1pt\relax
    \sbox0{\parbox{\dimen0}{\centering \parfillskip=0pt \nohyphens{#1}}}
    \dimen1=\dimexpr \ht0 + \dp0
  }

  % Go back the previous width (i.e. the minimum width without spillover).  
  \advance\dimen0 by 1pt\relax
  \parbox{\dimen0}{\centering \parfillskip=0pt \nohyphens{#1}}

\egroup}

\begin{document}

\squeeze{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam justo sem, 
malesuada ut ultricies ac.}

\end{document}

As a disclaimer, this is the first time I've had to program with TeX primitives, so I don't really know what I'm doing. I'm also really thankful to John for providing some working code I was able to build off of.

You must log in to answer this question.

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