12

From reading Knuth's TeX Book (Chapter 14 How TeX Breaks Paragraphs into Lines), I understand that entire paragraphs are read in and then "massaged" by the "line-breaking algorithm" in an optimal way (p 91):

One of typesetting system's chief duties is to take long sequences of words and to break it up into individual lines of the appropriate size. ... TeX chooses breakpoints in an interesting way that considers each paragraph in its entirety; the closing words of a paragraph can actually influence the appearance of the first line.

As a consequence, I assume that it flushes out the lines on-by-one after performing the optimization on a per-paragraph basis. Is it possible to interrupt this flushing of lines and say only output specific lines of the paragraph? I guess, almost similar to the way paragraphs are broken across pages.

For example, by means of defining an environment (say) showlines that one can use in the following way:

\begin{showlines}[lines=2]
  One of typesetting system's chief duties is to take long sequences 
  of words and to break it up into individual lines of the appropriate size. 
  \TeX\ chooses breakpoints in an interesting way that considers each paragraph 
  in its entirety; the closing words of a paragraph can actually influence 
  the appearance of the first line.
\end{showlines}

And TeX would only output the first two lines of the traditional output (possibly discarding the rest):

A paragraph of text

Note: Of interest here is to preserve the typesetting after line-breaking optimization has occurred, even in the case of hyphenation, since the tie should be with the shipout of lines. Of course, if there is another method of doing this (for example, possible sub-optimal line-breaking on a line-by-line basis), that would also work.

As a bonus, not just allowing to show the first n lines as in the example above (via [lines=n]), but allowing to show any combination of lines from the paragraph using notation like: [lines={1,4-5}] or [lines={-2}] for the last two lines.

3 Answers 3

10

A Lua(La)TeX solution is missing. Not anymore:

 \documentclass{article}
 \usepackage{luatexbase}
 \usepackage{luacode}
 \begin{luacode}
 twolines = function ( head )
   local orig_head = head
   local linecounter = 0

   while head do
     if head.id == 0 then
       linecounter = linecounter + 1
     end

     if linecounter > 1 then
       head.next = nil
       return true
     end

     head = head.next
   end
   return true
 end


 luatexbase.add_to_callback("post_linebreak_filter",twolines,"twolines")
 \end{luacode}

 \begin{document}
 \hsize 4in
      A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my
 whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine. I am
 so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be
 incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.
 \end{document}

This adds a callback function (twolines) to the post_linebreak_filter which counts the hlists (node id 0) found in a paragraph and just removes everything after the second hlist from the paragraph (a vlist - that is what orig_head is pointing to - not needed in this example).

The drawbacks:

  • It applies to every paragraph. To circumvent this, activate the callback only when needed. See the chickenize package by Arno Trautmann.
  • It has a memory leak. The nodes after the second hlist are still in a structure. On needs to call node.free(...) to remove the nodes from memory. See the reference manual (chapter 4, node library).
  • It doesn't work with iTeX [tinkle a bell].

Edit: here is a full solution, including the environment:

\documentclass{article}
\usepackage{luatexbase}
\usepackage{luacode}
\begin{luacode}
twolines = function ( head )
  local linecounter = 0

  while head do
    if head.id == 0 then
      linecounter = linecounter + 1
    end

    if linecounter >= showlinescount then
      local lastline=head

      head = head.next
      while head do
        local head_next = head.next
        node.free(head)
        head = head_next
      end

      lastline.next = nil
      return true
    end

    head = head.next
  end
  return true
end
\end{luacode}

\newenvironment{showlines}[1]
  {\directlua{showlinescount = tonumber('#1') 
              luatexbase.add_to_callback("post_linebreak_filter",twolines,"twolines")}}
  {\par
   \directlua{luatexbase.remove_from_callback("post_linebreak_filter","twolines")}}

\begin{document}
\hsize 4in

\newcommand\sample{A wonderful serenity has taken possession of my entire soul, like these sweet mornings
of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot,
which was created for the bliss of souls like mine. I am so happy, my dear friend, so absorbed in the exquisite
sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single
stroke at the present moment; and yet I feel that I never was a greater artist than now.}

\begin{showlines}{3}
\sample
\end{showlines}
\sample
\end{document}

This still doesn't work with iTeX [tinkle a bell] yet. But it has no memory leak anymore and it offeres a showlines environment.

3
  • 1
    You describe a fix for the memory leak. Why not implement it at once? Sep 11, 2011 at 12:45
  • @Andrey I like clean examples. Perhaps I add another (full) answer - but this should be left as an exercise to the reader :)
    – topskip
    Sep 11, 2011 at 14:35
  • 1
    @Andrey done :)
    – topskip
    Sep 11, 2011 at 15:09
7

When TeX breaks a paragraph into lines, it constructs a hbox for each line. This stack of hboxes can be placed in a vbox (so that there is actually a first hbox) and analyzed one hbox at a time using the \lastbox operation.

The following code was heavily influenced by TeX by Topic, subsection 5.9.6.

\documentclass{article}

\usepackage{lipsum}

\newbox\linebox

\newcount\mycount
\def\processline{% Underline every second line
  \global\advance\mycount by 1
  \ifodd\mycount
    \underline{\box\linebox}%
  \else
    \box\linebox
  \fi}

\def\splitlines{%
  \setbox\linebox\lastbox
  \ifvoid\linebox
    \noindent\ignorespaces
  \else
    \unskip\unpenalty
    \begingroup\splitlines\space\endgroup
    \processline
  \fi}

\long\def\doit#1{\vbox{#1\par\splitlines}}

\begin{document}

\doit{\lipsum[1]}

\end{document}

You can redefine \processline to suit your needs. For example, it might discard lines conditionally or \unhbox them for further changes. Selecting lines by number is now a matter of going through the hboxes and comparing numbers. It might be necessary to dissect the vbox twice.

5

The following code outputs the number of lines stated in the argument, provided all the lines have the same height (more precisely, the baselines are uniformly spaced):

\documentclass[a4paper]{article}

\makeatletter
\newbox\wernerboxa
\newbox\wernerboxb

\newenvironment{showlines}[1]
  {\vbadness=\@M\def\how@many{#1}\par
   \setbox\wernerboxa=\vbox\bgroup}
  {\egroup\setbox\wernerboxb=\vsplit\wernerboxa to\how@many\baselineskip
   \vbox{\unvbox\wernerboxb}}
\makeatother

\usepackage{lipsum}

\begin{document}
\begin{showlines}{5}
\lipsum
\end{showlines}
\end{document}

Repeating the \vsplit operation recursively can solve the problem of showing a specified list of lines.

Let's look at another approach that can cope with the case when there are higher objects in a line.

If the paragraph has no fancy things, but only plain text, a modification of an answer of mine will underline each line of the paragraph; actually the rule is added by the macro \add@rule, so it's possible to modify it in such a way that it draws the rule only if certain conditions are satisfied; the final number of lines is available in the macro \how@many. This works bottom up; it shouldn't be so complicated to add a check whether a line must be underlined.

\documentclass[a4paper]{article}
\usepackage{lipsum}

\makeatletter

\newif\ifboxended
\newbox\wernerbox
\def\add@rule{\kern0.8\p@\hrule\kern-1.2\p@}
\newenvironment{werner}
  {\par\setbox\wernerbox=\vbox\bgroup}
  {\par\xdef\how@many{\the\prevgraf}\egroup
   \message{The box has \how@many\space lines}%
   \underlinevbox}

\def\underlinevbox{\begingroup\global\setbox\@ne=\box\voidb@x
  \global\boxendedfalse
  \setbox\z@=\copy\wernerbox\relax\dounderlinevbox}
\def\dounderlinevbox{%
  \setbox\z@=\vbox{\unvbox\z@ 
    \ifcase\lastnodetype
% char node (can't remove)
    \or
% hlist node
    \setbox\tw@=\lastbox
    \def\next{\global\setbox\@ne=\vbox{\box\tw@\add@rule\unvbox\@ne}}%
    \or
% vlist node
    \setbox\tw@=\lastbox
    \def\next{\global\setbox\@ne=\vbox{\box\tw@\add@rule\unvbox\@ne}}%
    \or
% rule node (can't remove)
    \or
% ins node (can't remove)
    \or
% mark node (can't remove)
    \or
% adjust node (can't remove)
    \or
% ligature node (can't happen)
    \or
% disc node (can't happen)
    \or
% whatsit node (can't remove)
    \or
% math node (can't remove)
    \or
% glue node
    \skip@=\lastskip\unskip
    \def\next{\global\setbox\@ne=\vbox{\vskip\skip@\unvbox\@ne}}%
    \or
% kern node
    \dimen@=\lastkern\unkern
    \def\next{\global\setbox\@ne=\vbox{\kern\dimen@\unvbox\@ne}}%
    \or
% penalty node
    \count@=\lastpenalty\unpenalty
    \def\next{\global\setbox\@ne=\vbox{\penalty\count@\unvbox\@ne}}%
    \or
% unset node (can't happen)
    \or
% math mode node (can't remove)
    \else
% empty list
    \def\next{\global\boxendedtrue}
    \fi
    \next}
  \ifboxended
    \def\next{\unvbox\@ne\endgroup}
  \else
    \let\next\dounderlinevbox
  \fi
  \next}
\makeatother

\begin{document}
\begin{werner}
\lipsum[2]
\end{werner}
\end{document}

The code can be modified in order to output only specified lines. It should be noted that the dismantling happens from the bottom line up.

4
  • I'm not-so-much interested in underlining a specific line in a paragraph. I'm interested in only displaying the lines above the horizontal line in my example. Hence the question: " Is it possible to interrupt this flushing of lines and say only output specific lines of the paragraph? ... For example, by means of defining an environment (say) showlines, ... And TeX would only output the first two lines of the traditional output (possibly discarding the rest)"
    – Werner
    Sep 10, 2011 at 21:32
  • When I use your second approach with a preceding paragraph e.g. Hello World\par\begin{werner}\lipsum[1]\end{werner}\par Bye World, the space between Hello World and Lorem ipsum is missing (that would be there without the werner environment). Can this be prevented?
    – schtandard
    Apr 7, 2018 at 9:04
  • @schtandard Not sure what space you’re referring to
    – egreg
    Apr 7, 2018 at 10:47
  • @egreg I mean the vertical skip between the Hello World paragraph and the Lorem ipsum ... paragraph. It seems to disappear when using the werner environment compared to Hello World\par\lipsum[1]\par Bye World. (With e.g. \parskip=3ex this becomes very clear.)
    – schtandard
    Apr 7, 2018 at 11:10

You must log in to answer this question.

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