# Truncating multi-line text

We can use the truncate package to truncate a piece of text so that it fits e.g. in a 5cm wide box.

However, the package can only handle one line of text.

What if I have got a (possibly long) text that I would like to typeset in a 5cm wide minipage, ragged right, so that it uses at most 6 lines?

Basically, I would just like to ask LaTeX to typeset a piece of text so that we add line breaks as usual at most 5 times, and then the last line is truncated (with some kind of a truncation marker such as "…") if needed.

I can see that this would be very hard to implement with properly justified text, but for ragged right text this might be doable?

Edit: To clarify, I would like to produce an output that looks like this if the text is truncated:

Lorem ipsum dolor sit amet,
elit, sed do eiusmod tempor
incididunt ut labore et
dolore magna aliqua. Ut


(The truncation marker would be part of the last line, not a separate line.)

Here is a simple version for all e-TeX-based compilers that defines a command \shortvbox{number of lines}{width}{text}. The idea is based on the algorithm for dissecting a vertical box described in section 5.9.6 of TeX by Topic.

The first step is to set the box text as usual in a \vbox of the given width (actually a \vtop is used which aligns the box by its first rather than the last line, but this doesn't matter much). Note that this also allows for setting text fully justified. Next are two runs of the dissecting command \shortvbox@. The command runs a loop, at each iteration inspecting the last node that was added to the vbox. For that we use the e-TeX command \lastnodetype which gives us a number that indicates what kind of node is the last on the current list (hbox, glue, penalty etc.). According to the node type we use the appropriate commands to remove and later reassemble the final box.

As noted, two run are necessary here. This is because we traverse the box bottom-up but need the first n lines of the box for the final output. So on the first run (indicated by the parameter C) we just count the number of lines/hboxes that occur in the list and skip the reassembly. On the second run we actually reassemble the vbox, now counting line numbers backwards. All lines that occur after the desired number of lines in the final output are skipped, the others are added to the result box. The last step is to output the reassembled box with some \ldots added at the end.

The full example code:

\documentclass{article}

\usepackage{lipsum}

\makeatletter
\endlinechar=-1

\newcount\shortvbox@lines

% #1: Number of lines in the final box
% #2: Width of the box
% #3: Text
\long\def\shortvbox#1#2#3{
\begingroup
\setbox0=\vtop{\hsize=#2 #3\par}
\setbox2=\vtop{\unvcopy0\shortvbox@{#1}C}
\setbox2=\vtop{\unvcopy0\shortvbox@{#1}B}
\vtop{\hsize=#2\unvbox1\par\noindent\strut\ldots\par}
\endgroup
}

% #1: number of lines to reassemble
% #2: C = count lines only, otherwise also build box
\def\shortvbox@#1#2{
\begingroup
\global\setbox1=\vtop{}
\if#2C
\global\shortvbox@lines=0\relax
\fi
\loop
\count1=\lastnodetype
\ifnum\count1=1\relax  % hbox
\setbox10=\lastbox
\fi
\ifnum\count1=11\relax  % glue
\skip10=\lastskip
\unskip
\fi
\ifnum\count1=13\relax  % penalty
\count10=\lastpenalty
\unpenalty
\fi
%    \message{lastnodetype=\the\count1}
\edef\@stop{
\ifnum\count1<0 0\else
\ifnum\count1=1 1\else
\ifnum\count1=11 1\else
\ifnum\count1=13 1\else
0% stop early if we don't know how to handle the last node
\fi\fi\fi\fi
}
\if\@stop0
\else
\if#2C\else
\global\setbox1=\vtop{
\ifnum\shortvbox@lines<#1\relax
\ifnum\count1=1\relax  % hbox
\box10
\fi
\ifnum\count1=11\relax  % glue
\vskip\skip10
\fi
\ifnum\count1=13\relax  % penalty
\penalty\count10
\fi
\unvbox1
\fi
}
\fi
\repeat
\endgroup
}

\endlinechar=\^^M
\makeatother

\begin{document}

\fbox{\shortvbox{7}{5cm}{What if I have got a (possibly long) text that I would like to typeset in a 5cm wide minipage, \textbf{ragged right}, so that it uses at most 6 lines?
\vskip2ex
Basically, I would just like to ask \LaTeX to typeset a piece of text so that we add line breaks as usual at most 5 times, and then the last line is truncated (with some kind of a truncation marker such as \texttt{"..."}) if needed.}}
\hskip1em
\fbox{\shortvbox{4}{5cm}{\raggedright What if I have got a (possibly long) text that I would like to typeset in a 5cm wide minipage, \textbf{ragged right}, so that it uses at most 6 lines?
\vskip2ex
Basically, I would just like to ask \LaTeX to typeset a piece of text so that we add line breaks as usual at most 5 times, and then the last line is truncated (with some kind of a truncation marker such as \texttt{"..."}) if needed.}}

\shortvbox{4}{\linewidth}{\lipsum}

\end{document}


This version should be seen more like a proof-of-concept implementation. For example, in it's current version it only works for simple text paragraphs. More node types could be added, of course. Another thing is that hyphenation may occur right before the final line. I guess getting rid of that requires a bit more effort.

Based on my answer here: Is there a package to truncate text overflowing a fixed width AND height?

I introduce \trunclines{<lines>}{<width>}{<content>} to make truncated fully-justified box of given width and given lines of text plus an ellipsis line.

\documentclass{article}
\usepackage{trimclip,lipsum,stackengine}
\newcommand\trunclines[3]{%
\setbox0=\hbox{\parbox[t]{#2}{\strut#3}}%
\stackengine{0pt}{\clipbox{0pt \dimexpr\dp0-#1\baselineskip%
-\dp\strutbox+\baselineskip\relax%
{} 0in 0pt}{\copy0}}{...\strut}{U}{l}{F}{F}{S}%
}
\begin{document}
\textsf{original text in 5cm wide parbox}\smallskip

\parbox{5cm}{\lipsum[4]}\bigskip

\textsf{5cm wide parbox, truncated to 8 lines + ellipsis}\smallskip

\trunclines{8}{5cm}{\lipsum[4]}\bigskip

\textsf{or 3 lines, ragged right}\smallskip

\trunclines{3}{5cm}{\raggedright\lipsum[4]}
\end{document}
`