Test if paragraph has an overfull line

I'd like to test if the last paragraph has any overfull line.

The naive approach of looking at the \badness once the paragraph is finished does not work, as it will only give the badness of the last line. For instance, the following code shows a badness of 1000000 after Hello, world! but 0 after Hello, world, X!, simply because the last line (X!) is less than 15pt wide.

\hsize=15pt
\bye


I use this to do trial typesettings with different line-lengths until finding a line-length which is big enough to avoid overfull lines. Afterwards, I rescale the result to make it fit in the page. This would of course be a terrible idea for text, but the material I'm typesetting is maths over which I have no control and which may occasionally not split nicely into short lines. It is better to get something that is hard to read (small) rather than have it get out of the page. Because of this application, I'm also interested in a way to know the longest line in a paragraph.

• Happy new year! Not too sure if it works ok, but give it a try github.com/yannisl/phd/blob/master/test-badness.tex – Yiannis Lazarides Jan 2 '15 at 9:05
• @YiannisLazarides Thanks! If I understand correctly you split the paragraph into lines (with \lastbox, \unskip, \unpenalty) and re-typeset each line into a box of width \hsize to test their badness? You're actually more than half-way to an answer to the question «Obtain \badness or glue adjustment for each line». (I'd say it's more natural to initialize \maxbad globally since later assignments are global.) – Bruno Le Floch Jan 2 '15 at 9:28
• Why is there \hss in \eatlines? It is not there in \printlines. – Bruno Le Floch Jan 2 '15 at 9:42
• It is not needed. This is some old code from Will Robertson. \begin{minipage}[t]{55pt} \mbox{} \trypar\hyphenpenalty=-500\looseness=1. – Yiannis Lazarides Jan 2 '15 at 10:20
• 1. While not providing information in the log file, \hfuzz=0pt \overfullrule=7pt would give a visual clue for overfull lines in the document. 2. The ctan.org/pkg/adjustbox package might be helpful to solve the original problem (fit some math to some width). – Stephen Jun 21 '15 at 14:08

Since \badness is overwritten at each line break, the only way to keep track of line badness is to let TeX format a paragraph and then imitating it and reformat it manually line by line keeping track of what we're interested in.

This kind of decomposition-recomposition is not particularly hard to do, the only annoying part being restoring exact vertical spacing (in fact I still haven't got it quite right). A few questions already show the technique, see putting a box around each line of text in a paragraph and the linked ones.

But I won't bore you with some straightforward code cleanup from another answer. It seems to me that it would be most interesting (= fun for me) to automate the process you are doing by trial and error, instead of just facilitating it.

I wrote a macro that takes a paragraph and a line width range. Exploring it with a given step, it searches for the one minimizing the maximum badness present in the paragraph. Then it sets the paragraph at the optimal width, rescaled to fit the original one.

Here is the result:

On the left there's the ratio of original and optimal line width, i.e. the scale factor. On the right there's the badness of each line; the maximum is bold.

This is a bit more than what you asked for, but to do it I had to find the badness of each line (and keep track of the maximum). This is exactly what you have to do to find wether a paragraph contains an overfull line (by checking the maximum or maybe more elegantly breaking the recursion if one is found). The basic technique is there.

However I think that it's a bit more convenient to have something to search for the optimal width and output it instead of you doing it manually.

Also, there's another answer you might be interested in here.

Here is the code:

% Unessential but I don't want to
% reimplement \resizebox from scratch.
\input miniltx
\input graphics.sty

% internal variables
\newbox\linebox
\newif\ifmeasuring
\newif\ifverbose

% line width variables:
\newdimen\lwMIN % range minimum
\newdimen\lwMAX % range maximum
\newdimen\lwINC % increase step
\newdimen\lwCUR % currently evaluated
\newdimen\lwOPT % optimum

\newcount\lbMAX % max of the paragraph
\newcount\lbOPT % optimum

% \fit formats the following paragraph
% finding the line width in the given
% range by minimizing the maximum line
% badness and rescaling in to fit.
\def\fit#1#2#3#4\par{
\lwMIN=#1
\lwMAX=#2
\lwINC=#3
% find optimum
\measuringtrue
\lwCUR=\lwMIN
\lbOPT=1000000
{\find#4\par}
% format optimum
\verbosetrue
\measuringfalse
\lwCUR=\hsize
{\hsize=\lwOPT\global\setbox0=\vbox{#4\par\eat}}
% place optimum
\noindent\resizebox\hsize!%
{\ifverbose\llap{\bf$\the\hsize\over\the\lwOPT$~}\fi\box0}\par
}

% \find searches line widths in the
% given range to find the optimum to
% format the passed paragraph
\def\find#1\par{
% prepare measure at current line width
\hsize=\lwCUR
\lbMAX=0
\setbox0=\vbox{#1\par\eat}
% if in valid range
\ifnum\lwCUR<\lwMAX
% update optima
\ifnum\lbMAX<\lbOPT
\global\lbOPT=\lbMAX
\global\lwOPT=\lwCUR\fi
% evaluate next line width
\find#1\par\fi
}

% \eat eats lines of the last paragraph
% either measuring its maximum badness
% or re-setting it with annotations.
\def\eat{{
% eat line and measure its badness
\setbox\linebox=\lastbox
\setbox0=\hbox to \hsize{\unhcopy\linebox}
% if measuring update the maximum badness of the paragraph
\ifmeasuring\ifnum\lbCUR>\lbMAX\global\lbMAX=\lbCUR\fi\fi
% unless paragraph is over
\ifvoid\linebox\else
% nest call and process preceeding line
\unskip\unpenalty\eat
% unless measuring
\ifmeasuring\else
% set current line
\ifhmode\break\fi\noindent\box\linebox
\ifverbose\rlap{~$\ifnum\lbCUR=\lbOPT\bf\fi\the\lbCUR$}\fi\fi\fi